Models 44

GPT-6.1 Terra

by OpenAIReasoningClean
Open in new window

Running live in a sandboxed frame. Click inside it before using the keyboard.

How it went

Total time
1 min 23 s
Cost
$0.038
Output tokens
7,418
Spent thinking
505 tokens
Speed
103.9 tok/s
Console errors
0
First token after
11.3 s
First visible text after
11.3 s
Prompt tokens
493
Code size
22.3 KB
Finish reason
stop
Served by
OpenAI
Model id
openai/gpt-6.1-terra
Reasoning sent
{"effort":"medium"}
Max output
128,000 tokens
Generated
Nov 12, 2026

What it was asked

The prompt
Build an interactive Kanban board in vanilla HTML, CSS and JavaScript (no framework, no library). Requirements: - Three columns: "To do", "In progress", "Done", each showing its card count. - Pre-populate the board with 6 realistic cards spread across the columns so the first screenshot looks alive. - Each card has a title and a priority (low / medium / high) shown as a colored badge. - Add a card through a small modal form (title required), and delete a card with a button on it. - Drag and drop cards between columns with the mouse, with a clear visual drop indicator. - A search field that filters cards live by title. - A light / dark theme toggle. - State lives in memory only (no storage APIs).
System prompt, sent with every prompt
You are generating a self-contained web demo. It will be rendered automatically in a sandboxed iframe and compared side by side with the output of other AI models given the exact same task.

Output rules:
- Reply with exactly ONE fenced code block containing the complete file: ```html (or ```svg when the task asks for an SVG). No text before or after the code block.
- Everything must be inline: CSS in <style>, JavaScript in <script>. No external files, images, fonts or network requests, except the CDN URLs explicitly allowed by the task.
- Do not use localStorage, sessionStorage, cookies, IndexedDB, alert(), confirm(), prompt() or window.open(): they are blocked in the sandbox and will throw.
- The page must look right in a 1280×800 viewport and adapt gracefully down to 375px wide. No horizontal scrollbar.
- Something meaningful must be visible on first paint: a screenshot is taken automatically about 3 seconds after load, without any interaction.
- Everything that makes the demo interesting must be visible within the first seconds: nothing hidden behind a high score, a later level or a long play session.
- Games must start in a self-playing demo mode (a simple AI plays) behind a "Click or press a key to start" message, so the game can be judged in action without playing.
- The iframe only receives keyboard events after the user clicks it: if the demo uses the keyboard, display a clear "Click or press a key to start" message.
- The code must run with zero errors in the console of the latest Chrome.

Code run-1.html · 329 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Orbit — Project board</title>
<style>
  :root {
    color-scheme: light;
    --bg:#f7f7f5; --surface:#fff; --panel:#f0f1ef; --text:#202422;
    --muted:#858b86; --line:#e8ebe7; --purple:#7358d8; --purple-soft:#eeeafd;
    --shadow:0 2px 5px rgba(25,35,29,.025),0 8px 20px rgba(25,35,29,.025);
    --input:#fff; --overlay:rgba(24,28,25,.48);
  }
  [data-theme="dark"] {
    color-scheme:dark;
    --bg:#171a19; --surface:#242825; --panel:#1e221f; --text:#edf0ed;
    --muted:#9da59e; --line:#353b36; --purple:#a992ff; --purple-soft:#393047;
    --shadow:0 3px 12px rgba(0,0,0,.16); --input:#292e2a; --overlay:rgba(0,0,0,.65);
  }
  * { box-sizing:border-box; }
  body { margin:0; background:var(--bg); color:var(--text); font-family:Inter,ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif; font-size:14px; transition:background .2s,color .2s; }
  button,input,select { font:inherit; }
  button { cursor:pointer; }
  button:focus-visible,input:focus-visible,select:focus-visible { outline:3px solid #ad9cf3; outline-offset:3px; }
  svg.icon { width:18px; height:18px; fill:none; stroke:currentColor; stroke-width:1.7; stroke-linecap:round; stroke-linejoin:round; flex:none; }
  .topbar { height:72px; background:var(--surface); border-bottom:1px solid var(--line); display:flex; align-items:center; }
  .top-inner { width:100%; max-width:1376px; margin:auto; padding:0 48px; display:flex; align-items:center; gap:30px; }
  .brand { display:flex; align-items:center; gap:10px; font-weight:750; font-size:19px; letter-spacing:-.7px; }
  .brand-mark { width:31px; height:31px; border-radius:10px; background:#7358d8; display:grid; place-items:center; color:white; }
  .brand-mark svg { width:20px; height:20px; }
  .divider { height:24px; width:1px; background:var(--line); }
  .breadcrumb { display:flex; gap:10px; align-items:center; color:var(--muted); font-size:13px; }
  .breadcrumb strong { color:var(--text); font-weight:600; }
  .top-actions { margin-left:auto; display:flex; align-items:center; gap:17px; }
  .icon-button { border:0; background:transparent; color:var(--muted); display:grid; place-items:center; padding:7px; border-radius:8px; }
  .icon-button:hover { background:var(--panel); color:var(--text); }
  .avatar { width:32px; height:32px; border-radius:50%; display:grid; place-items:center; background:#d8e7dc; color:#486450; font-size:10px; font-weight:750; border:2px solid var(--surface); }
  .main { max-width:1376px; margin:auto; padding:44px 48px 70px; }
  .eyebrow { color:var(--purple); text-transform:uppercase; letter-spacing:1.4px; font-size:10px; font-weight:800; display:flex; align-items:center; gap:8px; }
  .eyebrow::before { content:""; width:7px; height:7px; background:var(--purple); border-radius:50%; }
  .heading-row { display:flex; align-items:flex-end; justify-content:space-between; gap:20px; margin-top:11px; }
  h1 { font-size:32px; letter-spacing:-1.25px; margin:0; line-height:1.15; font-weight:750; }
  .subtitle { color:var(--muted); font-size:14px; margin:9px 0 0; }
  .members { display:flex; align-items:center; padding-bottom:3px; }
  .members .avatar { margin-left:-7px; width:34px; height:34px; }
  .members .avatar:nth-child(2) { background:#f4dccd; color:#a06445; }
  .members .avatar:nth-child(3) { background:#ded8f4; color:#6e5c9e; }
  .member-label { color:var(--muted); font-size:12px; margin-left:9px; white-space:nowrap; }
  .toolbar { margin:33px 0 20px; display:flex; align-items:center; justify-content:space-between; gap:16px; }
  .view-tabs { display:flex; align-items:center; gap:22px; height:39px; }
  .view-tab { color:var(--text); font-weight:700; font-size:13px; height:100%; display:flex; align-items:center; gap:8px; border-bottom:2px solid var(--purple); }
  .view-tab svg { color:var(--purple); width:16px; }
  .toolbar-right { display:flex; gap:10px; }
  .search { display:flex; align-items:center; gap:9px; height:40px; width:230px; border:1px solid var(--line); background:var(--surface); border-radius:8px; padding:0 12px; color:var(--muted); }
  .search svg { width:16px; height:16px; }
  .search input { min-width:0; width:100%; color:var(--text); background:transparent; border:0; outline:none; font-size:12px; }
  .search input::placeholder { color:#a4aaa5; }
  .primary { border:0; border-radius:8px; background:#7358d8; color:white; font-weight:650; height:40px; padding:0 15px; display:inline-flex; align-items:center; justify-content:center; gap:7px; box-shadow:0 3px 8px rgba(115,88,216,.18); white-space:nowrap; }
  .primary:hover { background:#6248c3; }
  .primary svg { width:16px; height:16px; }
  .board { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:16px; align-items:start; }
  .column { background:var(--panel); border:1px solid transparent; border-radius:12px; padding:16px 14px 14px; min-height:458px; transition:border .15s,background .15s; }
  .column.drag-over { border-color:#a998e7; background:var(--purple-soft); }
  .column-head { display:flex; align-items:center; gap:9px; padding:1px 2px 15px; }
  .status-dot { width:9px; height:9px; border-radius:50%; background:#a8aeaa; box-shadow:0 0 0 3px rgba(160,170,162,.13); }
  .status-dot.progress { background:#d5a24f; box-shadow:0 0 0 3px rgba(213,162,79,.14); }
  .status-dot.done { background:#62a985; box-shadow:0 0 0 3px rgba(98,169,133,.14); }
  .column-title { font-size:13px; font-weight:750; margin:0; }
  .count { background:var(--surface); color:var(--muted); font-size:11px; font-weight:700; border-radius:5px; min-width:22px; height:21px; display:grid; place-items:center; }
  .column-add { margin-left:auto; border:0; background:transparent; color:var(--muted); display:grid; place-items:center; padding:2px; border-radius:5px; }
  .column-add:hover { color:var(--purple); background:var(--surface); }
  .column-add svg { width:17px; height:17px; }
  .card-list { display:flex; flex-direction:column; gap:10px; min-height:45px; }
  .card { background:var(--surface); border:1px solid var(--line); border-radius:9px; padding:15px; box-shadow:var(--shadow); cursor:grab; }
  .card:active { cursor:grabbing; }
  .card.dragging { opacity:.4; }
  .card-top { display:flex; align-items:center; justify-content:space-between; gap:8px; margin-bottom:12px; }
  .priority { display:inline-flex; align-items:center; gap:5px; border-radius:5px; padding:4px 7px; text-transform:capitalize; font-size:10px; font-weight:700; line-height:1; }
  .priority::before { content:""; width:5px; height:5px; border-radius:50%; background:currentColor; }
  .priority.high { background:#fcece9; color:#d36b58; }
  .priority.medium { background:#fcf2de; color:#b58a36; }
  .priority.low { background:#e8f3eb; color:#64a177; }
  .delete { border:0; background:transparent; color:#b5bbb6; padding:2px; display:grid; place-items:center; border-radius:5px; }
  .delete svg { width:15px; height:15px; }
  .delete:hover { color:#d36b58; background:#fcece9; }
  .card h3 { font-size:14px; line-height:1.4; letter-spacing:-.15px; margin:0; font-weight:700; overflow-wrap:anywhere; }
  .description { color:var(--muted); font-size:12px; line-height:1.5; margin:6px 0 0; }
  .card-footer { display:flex; align-items:center; gap:8px; margin-top:18px; }
  .tag { background:var(--panel); color:var(--muted); border-radius:5px; padding:5px 7px; font-size:10px; font-weight:650; }
  .card-date { display:flex; align-items:center; gap:4px; margin-left:auto; font-size:10px; color:var(--muted); white-space:nowrap; }
  .card-date svg { width:13px; height:13px; }
  .card-avatar { width:25px; height:25px; border-radius:50%; display:grid; place-items:center; font-size:9px; font-weight:750; background:#e7dcf5; color:#795c9c; margin-left:3px; }
  .card-avatar.green { background:#dcece0; color:#577a61; }
  .card-avatar.peach { background:#f5dfd4; color:#a56c50; }
  .placeholder { border:2px dashed #a38eea; border-radius:9px; height:78px; background:rgba(115,88,216,.06); }
  .empty { color:var(--muted); font-size:12px; line-height:1.5; text-align:center; padding:28px 8px; }
  .board-hint { display:flex; align-items:center; gap:7px; color:var(--muted); font-size:12px; margin-top:20px; }
  .board-hint svg { width:15px; height:15px; }
  .modal-backdrop { position:fixed; inset:0; z-index:10; background:var(--overlay); display:grid; place-items:center; padding:20px; }
  .modal-backdrop[hidden] { display:none; }
  .modal { width:min(100%,440px); background:var(--surface); border:1px solid var(--line); border-radius:14px; padding:25px; box-shadow:0 24px 70px rgba(0,0,0,.2); }
  .modal-head { display:flex; justify-content:space-between; align-items:flex-start; gap:15px; margin-bottom:23px; }
  .modal h2 { font-size:20px; letter-spacing:-.5px; margin:0 0 6px; }
  .modal p { color:var(--muted); font-size:12px; margin:0; }
  .field { display:block; margin:0 0 17px; font-size:12px; font-weight:700; }
  .field span { color:#d36b58; }
  .field input,.field select { display:block; width:100%; height:42px; margin-top:8px; border:1px solid var(--line); background:var(--input); color:var(--text); border-radius:7px; padding:0 11px; font-size:13px; }
  .form-row { display:grid; grid-template-columns:1fr 1fr; gap:12px; }
  .modal-actions { display:flex; justify-content:flex-end; gap:9px; margin-top:8px; }
  .secondary { height:40px; padding:0 15px; background:var(--surface); color:var(--text); border:1px solid var(--line); border-radius:8px; font-weight:650; }
  .secondary:hover { background:var(--panel); }
  @media(max-width:700px) {
    .top-inner { padding:0 20px; gap:15px; }
    .breadcrumb { display:none; }
    .divider { display:none; }
    .main { padding:32px 20px 50px; }
    h1 { font-size:28px; }
    .member-label { display:none; }
    .toolbar { align-items:flex-start; flex-direction:column; margin-top:26px; }
    .toolbar-right { width:100%; }
    .search { flex:1; width:auto; }
    .board { grid-template-columns:1fr; }
    .column { min-height:0; }
    .card-list { min-height:40px; }
    .board-hint { line-height:1.4; }
  }
</style>
</head>
<body>
<svg width="0" height="0" style="position:absolute" aria-hidden="true">
  <symbol id="i-plus" viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></symbol>
  <symbol id="i-search" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="m16 16 4 4"/></symbol>
  <symbol id="i-trash" viewBox="0 0 24 24"><path d="M4 7h16M10 11v6m4-6v6M6 7l1 13h10l1-13M9 7V4h6v3"/></symbol>
  <symbol id="i-sun" viewBox="0 0 24 24"><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"/></symbol>
  <symbol id="i-moon" viewBox="0 0 24 24"><path d="M20.5 14.3A8.5 8.5 0 0 1 9.7 3.5 8.5 8.5 0 1 0 20.5 14.3Z"/></symbol>
  <symbol id="i-board" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M9 4v16m6-16v16"/></symbol>
  <symbol id="i-calendar" viewBox="0 0 24 24"><rect x="4" y="5" width="16" height="15" rx="2"/><path d="M8 3v4m8-4v4M4 10h16"/></symbol>
  <symbol id="i-close" viewBox="0 0 24 24"><path d="M5 5l14 14M19 5 5 19"/></symbol>
  <symbol id="i-grip" viewBox="0 0 24 24"><path d="M8 5h.01M16 5h.01M8 12h.01M16 12h.01M8 19h.01M16 19h.01" stroke-width="3"/></symbol>
</svg>
<header class="topbar">
  <div class="top-inner">
    <div class="brand"><span class="brand-mark"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"><circle cx="12" cy="12" r="3"/><ellipse cx="12" cy="12" rx="9" ry="5" transform="rotate(-35 12 12)"/></svg></span>orbit</div>
    <div class="divider"></div>
    <div class="breadcrumb"><span>Workspace</span><span>/</span><strong>Product launch</strong></div>
    <div class="top-actions">
      <button class="icon-button" id="theme-toggle" aria-label="Switch to dark theme" title="Switch theme"><svg class="icon"><use href="#i-moon"></use></svg></button>
      <div class="avatar" title="Alex Morgan">AM</div>
    </div>
  </div>
</header>

<main class="main">
  <div class="eyebrow">Workspace / Product launch</div>
  <div class="heading-row">
    <div><h1>Project board</h1><p class="subtitle">A little progress each day adds up to big results.</p></div>
    <div class="members" aria-label="3 project members"><div class="avatar">AM</div><div class="avatar">JL</div><div class="avatar">SK</div><span class="member-label">Project team</span></div>
  </div>
  <div class="toolbar">
    <div class="view-tabs"><div class="view-tab"><svg class="icon"><use href="#i-board"></use></svg> Board view</div></div>
    <div class="toolbar-right">
      <label class="search"><svg class="icon"><use href="#i-search"></use></svg><input id="search" type="search" placeholder="Search tasks..." aria-label="Search tasks"></label>
      <button class="primary" id="add-task"><svg class="icon"><use href="#i-plus"></use></svg> Add task</button>
    </div>
  </div>
  <div class="board" id="board" aria-label="Kanban board"></div>
  <div class="board-hint"><svg class="icon"><use href="#i-grip"></use></svg> Drag and drop tasks to move them between columns</div>
</main>

<div class="modal-backdrop" id="modal" hidden>
  <div 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>Make room for your next great idea.</p></div><button class="icon-button" id="close-modal" type="button" aria-label="Close"><svg class="icon"><use href="#i-close"></use></svg></button></div>
    <form id="task-form">
      <label class="field">Task title <span>*</span><input id="task-title" type="text" maxlength="100" placeholder="What needs to be done?" required></label>
      <div class="form-row">
        <label class="field">Priority<select id="task-priority"><option value="low">Low</option><option value="medium" selected>Medium</option><option value="high">High</option></select></label>
        <label class="field">Status<select id="task-status"><option value="todo">To do</option><option value="progress">In progress</option><option value="done">Done</option></select></label>
      </div>
      <div class="modal-actions"><button type="button" class="secondary" id="cancel">Cancel</button><button type="submit" class="primary">Create task</button></div>
    </form>
  </div>
</div>

<script>
(() => {
  const columns = [
    {id:'todo', title:'To do', dot:''},
    {id:'progress', title:'In progress', dot:'progress'},
    {id:'done', title:'Done', dot:'done'}
  ];
  let tasks = [
    {id:1, status:'todo', title:'Plan the launch campaign', priority:'high', description:'Map out channels, milestones and key dates.', tag:'Marketing', date:'Jun 12', initials:'AM', color:'green'},
    {id:2, status:'todo', title:'Write website copy', priority:'medium', description:'Bring our story to life on the landing page.', tag:'Content', date:'Jun 14', initials:'JL', color:'peach'},
    {id:3, status:'todo', title:'Gather customer testimonials', priority:'low', description:'Reach out to a few of our early supporters.', tag:'Research', date:'Jun 18', initials:'SK', color:''},
    {id:4, status:'progress', title:'Design the landing page', priority:'high', description:'Create a welcoming home for the new product.', tag:'Design', date:'Jun 10', initials:'SK', color:''},
    {id:5, status:'progress', title:'Set up email onboarding', priority:'medium', description:'Help new users get off to a great start.', tag:'Product', date:'Jun 15', initials:'AM', color:'green'},
    {id:6, status:'done', title:'Define project goals', priority:'medium', description:'Align the team on what success looks like.', tag:'Planning', date:'Jun 05', initials:'JL', color:'peach'}
  ];
  let nextId = 7, draggedId = null, lastFocus = null;
  const board = document.getElementById('board');
  const search = document.getElementById('search');
  const modal = document.getElementById('modal');
  const form = document.getElementById('task-form');
  const titleInput = document.getElementById('task-title');
  const statusInput = document.getElementById('task-status');
  const icon = name => `<svg class="icon" aria-hidden="true"><use href="#i-${name}"></use></svg>`;
  const escapeHTML = value => String(value).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));

  function render() {
    const query = search.value.trim().toLowerCase();
    board.innerHTML = columns.map(col => {
      const all = tasks.filter(t => t.status === col.id);
      const shown = all.filter(t => t.title.toLowerCase().includes(query));
      return `<section class="column" data-column="${col.id}" aria-label="${col.title}">
        <div class="column-head"><span class="status-dot ${col.dot}"></span><h2 class="column-title">${col.title}</h2><span class="count" title="${all.length} total tasks">${query ? shown.length + '/' + all.length : all.length}</span><button class="column-add" type="button" data-add="${col.id}" aria-label="Add task to ${col.title}">${icon('plus')}</button></div>
        <div class="card-list" data-list="${col.id}">
          ${shown.map(t => `<article class="card" draggable="true" data-id="${t.id}" aria-label="${escapeHTML(t.title)}, ${t.priority} priority">
            <div class="card-top"><span class="priority ${t.priority}">${t.priority}</span><button class="delete" type="button" data-delete="${t.id}" aria-label="Delete ${escapeHTML(t.title)}" title="Delete task">${icon('trash')}</button></div>
            <h3>${escapeHTML(t.title)}</h3>
            ${t.description ? `<p class="description">${escapeHTML(t.description)}</p>` : ''}
            <div class="card-footer"><span class="tag">${escapeHTML(t.tag || 'Task')}</span>${t.date ? `<span class="card-date">${icon('calendar')}${escapeHTML(t.date)}</span>` : '<span class="card-date">Just added</span>'}<span class="card-avatar ${t.color || ''}">${escapeHTML(t.initials || 'AM')}</span></div>
          </article>`).join('')}
          ${shown.length === 0 ? `<div class="empty">${query ? 'No matching tasks' : 'No tasks here yet'}<br>${query ? 'Try another search.' : 'Add one or drag one here.'}</div>` : ''}
        </div>
      </section>`;
    }).join('');
  }

  function openModal(status) {
    lastFocus = document.activeElement;
    form.reset();
    statusInput.value = status || 'todo';
    modal.hidden = false;
    requestAnimationFrame(() => titleInput.focus());
  }
  function closeModal() {
    modal.hidden = true;
    if (lastFocus && document.contains(lastFocus)) lastFocus.focus();
  }

  document.getElementById('add-task').addEventListener('click', () => openModal('todo'));
  document.getElementById('close-modal').addEventListener('click', closeModal);
  document.getElementById('cancel').addEventListener('click', closeModal);
  modal.addEventListener('click', e => { if (e.target === modal) closeModal(); });
  document.addEventListener('keydown', e => { if (e.key === 'Escape' && !modal.hidden) closeModal(); });
  form.addEventListener('submit', e => {
    e.preventDefault();
    const title = titleInput.value.trim();
    if (!title) { titleInput.setCustomValidity('Please enter a task title.'); titleInput.reportValidity(); return; }
    titleInput.setCustomValidity('');
    tasks.push({id:nextId++, status:statusInput.value, title, priority:document.getElementById('task-priority').value, tag:'Task', initials:'AM', color:'green'});
    search.value = '';
    render();
    closeModal();
  });
  titleInput.addEventListener('input', () => titleInput.setCustomValidity(''));
  search.addEventListener('input', render);
  board.addEventListener('click', e => {
    const add = e.target.closest('[data-add]');
    const del = e.target.closest('[data-delete]');
    if (add) openModal(add.dataset.add);
    if (del) { tasks = tasks.filter(t => t.id !== Number(del.dataset.delete)); render(); }
  });

  board.addEventListener('dragstart', e => {
    const card = e.target.closest('.card');
    if (!card) return;
    draggedId = Number(card.dataset.id);
    e.dataTransfer.effectAllowed = 'move';
    e.dataTransfer.setData('text/plain', String(draggedId));
    requestAnimationFrame(() => card.classList.add('dragging'));
  });
  board.addEventListener('dragover', e => {
    const column = e.target.closest('.column');
    if (!column || draggedId === null) return;
    e.preventDefault();
    e.dataTransfer.dropEffect = 'move';
    board.querySelectorAll('.column').forEach(c => c.classList.toggle('drag-over', c === column));
    let placeholder = board.querySelector('.placeholder');
    if (!placeholder) { placeholder = document.createElement('div'); placeholder.className = 'placeholder'; }
    const list = column.querySelector('.card-list');
    const cards = [...list.querySelectorAll('.card:not(.dragging)')];
    const next = cards.find(card => e.clientY < card.getBoundingClientRect().top + card.offsetHeight / 2);
    list.insertBefore(placeholder, next || null);
  });
  board.addEventListener('drop', e => {
    const column = e.target.closest('.column');
    if (!column || draggedId === null) return;
    e.preventDefault();
    const task = tasks.find(t => t.id === draggedId);
    const placeholder = column.querySelector('.placeholder');
    if (task) {
      let nextCard = placeholder && placeholder.nextElementSibling;
      while (nextCard && !nextCard.classList.contains('card')) nextCard = nextCard.nextElementSibling;
      const nextIdInList = nextCard ? Number(nextCard.dataset.id) : null;
      tasks = tasks.filter(t => t.id !== draggedId);
      task.status = column.dataset.column;
      const index = nextIdInList === null ? -1 : tasks.findIndex(t => t.id === nextIdInList);
      if (index >= 0) tasks.splice(index, 0, task);
      else {
        const lastInColumn = tasks.reduce((last, t, i) => t.status === task.status ? i : last, -1);
        tasks.splice(lastInColumn + 1, 0, task);
      }
    }
    draggedId = null;
    render();
  });
  board.addEventListener('dragend', () => { draggedId = null; render(); });
  board.addEventListener('dragleave', e => {
    if (!board.contains(e.relatedTarget)) {
      board.querySelectorAll('.column').forEach(c => c.classList.remove('drag-over'));
      const placeholder = board.querySelector('.placeholder');
      if (placeholder) placeholder.remove();
    }
  });

  document.getElementById('theme-toggle').addEventListener('click', e => {
    const dark = document.documentElement.dataset.theme !== 'dark';
    document.documentElement.dataset.theme = dark ? 'dark' : 'light';
    const button = e.currentTarget;
    button.innerHTML = icon(dark ? 'sun' : 'moon');
    button.setAttribute('aria-label', dark ? 'Switch to light theme' : 'Switch to dark theme');
  });
  render();
})();
</script>
</body>
</html>