Models 44

Muse Ember

Open in new window

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

How it went

Total time
14.6 s
Cost
$0.0035
Output tokens
6,812
Spent thinking
45 tokens
Speed
513.7 tok/s
Console errors
0
First token after
1.3 s
First visible text after
1.3 s
Prompt tokens
511
Code size
21.7 KB
Finish reason
stop
Served by
Meta
Model id
meta/muse-ember
Reasoning sent
{"effort":"minimal"}
Max output
943,718 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 · 307 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Flowboard — Kanban</title>
<style>
  *{box-sizing:border-box;margin:0;padding:0}
  :root{
    --bg:#f4f5f7; --bg-grad: radial-gradient(900px 500px at 15% -10%, #dbeafe 0%, transparent 60%), radial-gradient(800px 500px at 100% 0%, #fce7f3 0%, transparent 55%), #f4f5f7;
    --surface:#ffffff; --surface2:#f1f2f6; --border:#e6e7ee; --text:#171a26; --muted:#6b7280; --muted2:#9aa0b0;
    --accent:#6366f1; --accent2:#4f46e5; --shadow:0 6px 24px rgba(23,26,38,.08),0 1px 3px rgba(23,26,38,.06);
    --shadow-lg:0 20px 50px rgba(23,26,38,.18); --input:#fff; --hover:#f7f7fa;
    --low:#059669; --low-bg:#ecfdf5; --low-bd:#a7f3d0;
    --med:#d97706; --med-bg:#fffbeb; --med-bd:#fde68a;
    --high:#dc2626; --high-bg:#fef2f2; --high-bd:#fecaca;
    --drop:#6366f1; --col-bg:#eceef3;
  }
  html[data-theme="dark"]{
    --bg:#0d0f17; --bg-grad: radial-gradient(900px 500px at 15% -10%, #1e1b4b 0%, transparent 60%), radial-gradient(800px 500px at 100% 0%, #312e81 0%, transparent 55%), #0d0f17;
    --surface:#171a28; --surface2:#1f2336; --border:#2a2f47; --text:#eef0ff; --muted:#9aa3c0; --muted2:#636b8a;
    --accent:#818cf8; --accent2:#6366f1; --shadow:0 6px 24px rgba(0,0,0,.4),0 1px 3px rgba(0,0,0,.3);
    --shadow-lg:0 20px 50px rgba(0,0,0,.6); --input:#1c2033; --hover:#21263d;
    --low:#34d399; --low-bg:#052e22; --low-bd:#065f46;
    --med:#fbbf24; --med-bg:#3a2a05; --med-bd:#92400e;
    --high:#f87171; --high-bg:#3b1111; --high-bd:#7f1d1d;
    --col-bg:#151826;
  }
  body{font-family:Inter,ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;background:var(--bg-grad);background-color:var(--bg);color:var(--text);min-height:100vh;transition:background .3s,color .3s}
  a{color:var(--accent)}
  .wrap{max-width:1280px;margin:0 auto;padding:28px 24px 40px}
  /* Header */
  header.top{display:flex;align-items:center;gap:16px;flex-wrap:wrap;margin-bottom:22px}
  .logo{display:flex;align-items:center;gap:12px;margin-right:auto}
  .logo-mark{width:42px;height:42px;border-radius:11px;background:linear-gradient(135deg,#6366f1,#a855f7);display:grid;place-items:center;color:#fff;box-shadow:0 8px 20px rgba(99,102,241,.35);flex:0 0 auto}
  .logo h1{font-size:23px;letter-spacing:-.02em;line-height:1}
  .logo p{font-size:12.5px;color:var(--muted);margin-top:4px}
  .search{position:relative;flex:1 1 280px;max-width:380px}
  .search svg{position:absolute;left:13px;top:50%;transform:translateY(-50%);color:var(--muted2);pointer-events:none}
  .search input{width:100%;padding:11px 36px 11px 40px;border:1.5px solid var(--border);border-radius:11px;background:var(--surface);color:var(--text);font:inherit;font-size:14px;outline:none;transition:.2s}
  .search input:focus{border-color:var(--accent);box-shadow:0 0 0 4px rgba(99,102,241,.15)}
  .search input::placeholder{color:var(--muted2)}
  .clear-x{position:absolute;right:8px;top:50%;transform:translateY(-50%);border:none;background:var(--surface2);color:var(--muted);width:24px;height:24px;border-radius:50%;cursor:pointer;display:none;place-items:center;font-size:14px;line-height:1}
  .clear-x.show{display:grid}
  .btn{border:none;cursor:pointer;font:inherit;font-weight:600;font-size:14px;border-radius:11px;padding:11px 16px;display:inline-flex;align-items:center;gap:8px;transition:.18s;white-space:nowrap}
  .btn-primary{background:var(--accent);color:#fff;box-shadow:0 6px 16px rgba(99,102,241,.3)}
  .btn-primary:hover{background:var(--accent2);transform:translateY(-1px)}
  .btn-ghost{background:var(--surface);color:var(--text);border:1.5px solid var(--border)}
  .btn-ghost:hover{background:var(--hover)}
  .theme-btn{width:44px;height:44px;padding:0;justify-content:center;border-radius:50%;font-size:18px}
  .stats{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:18px}
  .stat{background:var(--surface);border:1px solid var(--border);border-radius:11px;padding:9px 14px;font-size:13px;color:var(--muted);display:flex;gap:8px;align-items:center}
  .stat b{color:var(--text);font-size:15px}
  .dot{width:8px;height:8px;border-radius:50%}
  /* Board */
  .board{display:grid;grid-template-columns:repeat(3,1fr);gap:18px;align-items:start}
  .col{background:var(--col-bg);border:1px solid var(--border);border-radius:16px;padding:14px;min-height:340px;transition:.2s}
  .col.drag-over{border-color:var(--accent);box-shadow:0 0 0 4px rgba(99,102,241,.15);background:var(--surface2)}
  .col-head{display:flex;align-items:center;gap:9px;padding:4px 6px 12px}
  .col-ico{width:28px;height:28px;border-radius:8px;display:grid;place-items:center;font-size:14px;flex:0 0 auto}
  .col h2{font-size:13px;text-transform:uppercase;letter-spacing:.07em;flex:1}
  .count{background:var(--surface);border:1px solid var(--border);color:var(--muted);font-size:12px;font-weight:700;min-width:26px;height:24px;padding:0 7px;border-radius:999px;display:grid;place-items:center}
  .add-mini{border:none;background:transparent;color:var(--muted);font-size:20px;width:28px;height:28px;border-radius:8px;cursor:pointer;line-height:1}
  .add-mini:hover{background:var(--surface);color:var(--text)}
  .cards{display:flex;flex-direction:column;gap:11px;min-height:80px}
  .empty{border:1.5px dashed var(--border);border-radius:12px;padding:22px 12px;text-align:center;color:var(--muted2);font-size:13px;line-height:1.5}
  .card{background:var(--surface);border:1px solid var(--border);border-radius:13px;padding:14px;box-shadow:var(--shadow);cursor:grab;position:relative;transition:box-shadow .18s,transform .18s,opacity .18s}
  .card:hover{transform:translateY(-1px);box-shadow:0 10px 26px rgba(23,26,38,.1)}
  .card:active{cursor:grabbing}
  .card.dragging{opacity:.35;transform:rotate(1.5deg) scale(.98);box-shadow:none}
  .card.drag-src{opacity:.35}
  .card h3{font-size:14.5px;line-height:1.4;padding-right:28px;letter-spacing:-.01em}
  .card p{font-size:13px;color:var(--muted);line-height:1.55;margin-top:6px}
  .card-foot{display:flex;align-items:center;gap:8px;margin-top:12px}
  .badge{font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;padding:4px 9px;border-radius:999px;border:1px solid;display:inline-flex;align-items:center;gap:5px}
  .badge i{width:6px;height:6px;border-radius:50%;background:currentColor;display:inline-block}
  .badge.low{color:var(--low);background:var(--low-bg);border-color:var(--low-bd)}
  .badge.medium{color:var(--med);background:var(--med-bg);border-color:var(--med-bd)}
  .badge.high{color:var(--high);background:var(--high-bg);border-color:var(--high-bd)}
  .meta{margin-left:auto;font-size:11.5px;color:var(--muted2)}
  .del{position:absolute;top:9px;right:9px;width:26px;height:26px;border:none;border-radius:7px;background:transparent;color:var(--muted2);cursor:pointer;font-size:15px;display:grid;place-items:center;opacity:0;transition:.15s}
  .card:hover .del,.del:focus-visible{opacity:1}
  .del:hover{background:var(--high-bg);color:var(--high)}
  .drop-line{height:3px;border-radius:99px;background:var(--drop);box-shadow:0 0 0 3px rgba(99,102,241,.2);margin:-5px 0}
  .skeleton{border-radius:13px;border:2px dashed rgba(99,102,241,.45);background:rgba(99,102,241,.06);min-height:92px;display:grid;place-items:center;color:var(--accent);font-size:12.5px;font-weight:600}
  /* Modal */
  .overlay{position:fixed;inset:0;background:rgba(13,15,23,.55);backdrop-filter:blur(4px);display:none;place-items:center;padding:20px;z-index:50}
  .overlay.open{display:grid}
  .modal{background:var(--surface);border:1px solid var(--border);border-radius:18px;width:100%;max-width:460px;padding:24px;box-shadow:var(--shadow-lg);animation:pop .22s ease}
  @keyframes pop{from{transform:scale(.96) translateY(8px);opacity:0}to{transform:none;opacity:1}}
  .modal h2{font-size:19px;letter-spacing:-.02em}
  .modal .sub{font-size:13.5px;color:var(--muted);margin:6px 0 18px}
  label.fld{display:block;font-size:12.5px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;color:var(--muted);margin:14px 0 7px}
  label.fld:first-of-type{margin-top:0}
  .modal input,.modal textarea,.modal select{width:100%;padding:11px 13px;border:1.5px solid var(--border);border-radius:11px;background:var(--input);color:var(--text);font:inherit;font-size:14px;outline:none;transition:.2s;resize:vertical}
  .modal input:focus,.modal textarea:focus,.modal select:focus{border-color:var(--accent);box-shadow:0 0 0 4px rgba(99,102,241,.15)}
  .modal input.err{border-color:var(--high);box-shadow:0 0 0 4px rgba(220,38,38,.12)}
  .hint{font-size:12.5px;color:var(--high);margin-top:6px;display:none}
  .hint.show{display:block}
  .pri-row{display:flex;gap:8px}
  .pri-opt{flex:1;border:1.5px solid var(--border);background:var(--surface);border-radius:11px;padding:10px;cursor:pointer;font:inherit;font-size:13px;font-weight:600;color:var(--muted);display:flex;align-items:center;justify-content:center;gap:6px;transition:.15s}
  .pri-opt.sel-low{border-color:var(--low);background:var(--low-bg);color:var(--low)}
  .pri-opt.sel-medium{border-color:var(--med);background:var(--med-bg);color:var(--med)}
  .pri-opt.sel-high{border-color:var(--high);background:var(--high-bg);color:var(--high)}
  .modal-actions{display:flex;gap:10px;justify-content:flex-end;margin-top:22px}
  .col-sel{margin-top:2px}
  footer{margin-top:22px;font-size:12.5px;color:var(--muted2);text-align:center}
  .no-results{display:none;text-align:center;color:var(--muted);padding:34px 16px;background:var(--surface);border:1px dashed var(--border);border-radius:14px;grid-column:1/-1;font-size:14px}
  .no-results.show{display:block}
  @media(max-width:860px){.board{grid-template-columns:1fr}.col{min-height:0}.wrap{padding:20px 14px 32px}.logo p{display:none}}
</style>
</head>
<body>
<div class="wrap">
  <header class="top">
    <div class="logo">
      <div class="logo-mark" aria-hidden="true">
        <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><rect x="3" y="4" width="5" height="16" rx="1.5"/><rect x="10" y="4" width="5" height="10" rx="1.5"/><rect x="17" y="4" width="4" height="13" rx="1.5"/></svg>
      </div>
      <div><h1>Flowboard</h1><p>Drag cards between columns · in-memory state</p></div>
    </div>
    <div class="search">
      <svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>
      <input id="search" type="search" placeholder="Search cards by title…" aria-label="Search cards">
      <button class="clear-x" id="clearSearch" aria-label="Clear search">×</button>
    </div>
    <button class="btn btn-ghost theme-btn" id="themeBtn" aria-label="Toggle dark / light theme" title="Toggle theme"><span id="themeIco">🌙</span></button>
    <button class="btn btn-primary" id="addBtn">+ Add card</button>
  </header>

  <div class="stats" id="stats"></div>
  <main class="board" id="board"></main>
  <div class="no-results" id="noResults">🔍 No cards match your search. <button id="resetSearch" style="border:none;background:none;color:var(--accent);font:inherit;font-weight:700;cursor:pointer;text-decoration:underline">Clear search</button></div>
  <footer>Tip: drag a card onto another column — the dashed slot shows exactly where it will land. New cards live only in memory.</footer>
</div>

<div class="overlay" id="overlay" role="dialog" aria-modal="true" aria-labelledby="modalTitle">
  <form class="modal" id="cardForm" novalidate>
    <h2 id="modalTitle">Add a new card</h2>
    <p class="sub">It will appear at the top of the selected column.</p>
    <label class="fld" for="fTitle">Title <span style="color:var(--high)">*</span></label>
    <input id="fTitle" type="text" placeholder="e.g. Design onboarding flow" autocomplete="off" maxlength="90">
    <div class="hint" id="titleHint">A title is required.</div>
    <label class="fld" for="fDesc">Description <span style="text-transform:none;font-weight:500">(optional)</span></label>
    <textarea id="fDesc" rows="3" placeholder="Add a short note, acceptance criteria, links…" maxlength="220"></textarea>
    <label class="fld">Priority</label>
    <div class="pri-row" id="priRow">
      <button type="button" class="pri-opt" data-p="low">● Low</button>
      <button type="button" class="pri-opt sel-medium" data-p="medium">● Medium</button>
      <button type="button" class="pri-opt" data-p="high">● High</button>
    </div>
    <label class="fld" for="fCol">Column</label>
    <select id="fCol" class="col-sel">
      <option value="todo">To do</option><option value="progress">In progress</option><option value="done">Done</option>
    </select>
    <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>

<script>
(function(){
  "use strict";
  const COLS=[
    {id:"todo",name:"To do",color:"#64748b",bg:"#e2e8f0",ico:"○"},
    {id:"progress",name:"In progress",color:"#d97706",bg:"#fef3c7",ico:"◐"},
    {id:"done",name:"Done",color:"#059669",bg:"#d1fae5",ico:"●"}
  ];
  const ICONS={todo:"📋",progress:"⚡",done:"✅"};
  let seq=100;
  let cards=[
    {id:1,col:"todo",title:"Design new onboarding flow",desc:"Sketch 3 variants in Figma before Friday's design review.",p:"high",tag:"UX · 2d"},
    {id:2,col:"todo",title:"Fix contrast on settings page",desc:"Muted text fails WCAG AA on mobile.",p:"medium",tag:"A11y"},
    {id:3,col:"progress",title:"Build drag-and-drop board UI",desc:"Mouse events + placeholder indicator for drop position.",p:"high",tag:"Frontend"},
    {id:4,col:"progress",title:"Write API for card search",desc:"GET /cards?q= with title filter and pagination.",p:"medium",tag:"Backend · 4d"},
    {id:5,col:"done",title:"Ship dark mode toggle",desc:"CSS variables + prefers-color-scheme default.",p:"low",tag:"Shipped Tue"},
    {id:6,col:"done",title:"Audit bundle size",desc:"Tree-shook icons, dropped 84 KB gzipped.",p:"low",tag:"Perf"}
  ];
  let query="", draggedId=null, selectedPri="medium", presetCol="todo";

  const board=document.getElementById("board"), stats=document.getElementById("stats"),
        searchEl=document.getElementById("search"), clearBtn=document.getElementById("clearSearch"),
        noRes=document.getElementById("noResults"), overlay=document.getElementById("overlay"),
        form=document.getElementById("cardForm"), titleEl=document.getElementById("fTitle"),
        descEl=document.getElementById("fDesc"), colEl=document.getElementById("fCol"),
        hint=document.getElementById("titleHint");

  function visible(){ return cards.filter(c=>c.title.toLowerCase().includes(query)); }
  function esc(s){ return s.replace(/[&<>"']/g,m=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}[m])); }

  function render(){
    const vis=visible();
    board.innerHTML="";
    COLS.forEach(col=>{
      const list=vis.filter(c=>c.col===col.id);
      const el=document.createElement("section");
      el.className="col"; el.dataset.col=col.id;
      el.innerHTML=`<div class="col-head">
        <span class="col-ico" style="background:${col.bg};color:${col.color}">${ICONS[col.id]}</span>
        <h2>${col.name}</h2><span class="count" aria-label="${list.length} cards">${list.length}</span>
        <button class="add-mini" data-add="${col.id}" title="Add card to ${col.name}" aria-label="Add card to ${col.name}">+</button></div>
        <div class="cards"></div>`;
      const wrap=el.querySelector(".cards");
      if(!list.length){
        const e=document.createElement("div"); e.className="empty";
        e.innerHTML=query?`Nothing here matches “${esc(query)}”`:`Drop cards here<br><span style="font-size:12px">or click + to add one</span>`;
        wrap.appendChild(e);
      }
      list.forEach(c=>{
        const card=document.createElement("article");
        card.className="card"; card.draggable=true; card.dataset.id=c.id;
        card.setAttribute("aria-label",c.title);
        card.innerHTML=`<button class="del" data-del="${c.id}" title="Delete card" aria-label="Delete ${esc(c.title)}">×</button>
          <h3>${esc(c.title)}</h3>${c.desc?`<p>${esc(c.desc)}</p>`:""}
          <div class="card-foot"><span class="badge ${c.p}"><i></i>${c.p}</span><span class="meta">${esc(c.tag||"")}</span></div>`;
        card.addEventListener("dragstart",e=>{draggedId=c.id;card.classList.add("dragging");e.dataTransfer.effectAllowed="move";try{e.dataTransfer.setData("text/plain",""+c.id)}catch(_){}});
        card.addEventListener("dragend",()=>{cleanupDrop();});
        card.addEventListener("dragover",e=>{e.preventDefault();e.stopPropagation();showIndicator(wrap,card);});
        card.addEventListener("drop",e=>{e.preventDefault();e.stopPropagation();moveTo(c.id,col.id,beforeId(card));});
        wrap.appendChild(card);
      });
      wrap.addEventListener("dragover",e=>{e.preventDefault();});
      wrap.addEventListener("drop",e=>{e.preventDefault();if(e.target===wrap||e.target.classList.contains("empty"))moveTo(null,col.id,null);});
      el.addEventListener("dragover",e=>{e.preventDefault();el.classList.add("drag-over");});
      el.addEventListener("dragleave",e=>{if(!el.contains(e.relatedTarget))el.classList.remove("drag-over");});
      el.addEventListener("drop",e=>{e.preventDefault();el.classList.remove("drag-over");});
      board.appendChild(el);
    });
    // stats
    const total=cards.length, hi=cards.filter(c=>c.p==="high").length, doneN=cards.filter(c=>c.col==="done").length;
    stats.innerHTML=`<span class="stat"><span class="dot" style="background:var(--accent)"></span><b>${total}</b> total cards</span>
      <span class="stat"><span class="dot" style="background:var(--high)"></span><b>${hi}</b> high priority</span>
      <span class="stat"><span class="dot" style="background:var(--low)"></span><b>${doneN}</b> completed (${total?Math.round(doneN/total*100):0}%)</span>
      ${query?`<span class="stat">🔍 <b>${vis.length}</b> match “${esc(query)}”</span>`:""}`;
    noRes.classList.toggle("show", vis.length===0 && cards.length>0);
    clearBtn.classList.toggle("show", !!query);
    board.querySelectorAll("[data-add]").forEach(b=>b.addEventListener("click",()=>openModal(b.dataset.add)));
    board.querySelectorAll("[data-del]").forEach(b=>b.addEventListener("click",e=>{e.stopPropagation();cards=cards.filter(c=>c.id!=b.dataset.del);render();}));
  }
  function beforeId(cardEl){ const id=+cardEl.dataset.id; return id===draggedId?null:id; }
  function showIndicator(wrap,beforeEl){
    cleanupDrop(true);
    const line=document.createElement("div"); line.className="drop-line";
    if(beforeEl) wrap.insertBefore(line,beforeEl);
    else { const ph=document.createElement("div"); ph.className="skeleton"; ph.textContent="Drop here"; wrap.appendChild(ph); }
    board.querySelectorAll(".col").forEach(c=>c.classList.add("drag-over"));
  }
  function cleanupDrop(keep){
    document.querySelectorAll(".drop-line,.skeleton").forEach(n=>n.remove());
    document.querySelectorAll(".card.dragging").forEach(n=>n.classList.remove("dragging"));
    if(!keep){ document.querySelectorAll(".col.drag-over").forEach(n=>n.classList.remove("drag-over")); draggedId=null; }
  }
  function moveTo(beforeCardId,colId,insertBeforeId){
    if(draggedId==null) return;
    const idx=cards.findIndex(c=>c.id===draggedId); if(idx<0) return;
    const [card]=cards.splice(idx,1); card.col=colId;
    if(insertBeforeId!=null){
      const ti=cards.findIndex(c=>c.id===insertBeforeId);
      if(ti>=0) cards.splice(ti,0,card); else cards.push(card);
    } else if(beforeCardId!=null){
      const ti=cards.findIndex(c=>c.id===beforeCardId);
      if(ti>=0) cards.splice(ti,0,card); else cards.push(card);
    } else {
      // append to end of target column (before first card of next column in array order is hard; push then stable sort by visual order)
      cards.push(card);
    }
    draggedId=null; cleanupDrop(); render();
  }

  /* Search — live filter */
  searchEl.addEventListener("input",()=>{query=searchEl.value.trim().toLowerCase();render();});
  clearBtn.addEventListener("click",()=>{searchEl.value="";query="";render();searchEl.focus();});
  document.getElementById("resetSearch").addEventListener("click",()=>{searchEl.value="";query="";render();});

  /* Theme */
  const themeBtn=document.getElementById("themeBtn"), themeIco=document.getElementById("themeIco");
  themeBtn.addEventListener("click",()=>{
    const dark=document.documentElement.dataset.theme==="dark";
    document.documentElement.dataset.theme=dark?"":"dark";
    themeIco.textContent=dark?"🌙":"☀️";
  });

  /* Modal */
  function openModal(col){ presetCol=col||"todo"; colEl.value=presetCol; titleEl.value=""; descEl.value=""; titleEl.classList.remove("err"); hint.classList.remove("show"); setPri("medium"); overlay.classList.add("open"); setTimeout(()=>titleEl.focus(),30); }
  function closeModal(){ overlay.classList.remove("open"); }
  document.getElementById("addBtn").addEventListener("click",()=>openModal("todo"));
  document.getElementById("cancelBtn").addEventListener("click",closeModal);
  overlay.addEventListener("click",e=>{ if(e.target===overlay) closeModal(); });
  document.addEventListener("keydown",e=>{ if(e.key==="Escape") closeModal(); });
  function setPri(p){ selectedPri=p; document.querySelectorAll(".pri-opt").forEach(b=>{ b.className="pri-opt"+(b.dataset.p===p?" sel-"+p:""); }); }
  document.getElementById("priRow").addEventListener("click",e=>{ const b=e.target.closest(".pri-opt"); if(b) setPri(b.dataset.p); });
  titleEl.addEventListener("input",()=>{ titleEl.classList.remove("err"); hint.classList.remove("show"); });
  form.addEventListener("submit",e=>{
    e.preventDefault();
    const t=titleEl.value.trim();
    if(!t){ titleEl.classList.add("err"); hint.classList.add("show"); titleEl.focus(); return; }
    cards.unshift({id:++seq,col:colEl.value,title:t,desc:descEl.value.trim(),p:selectedPri,tag:"New"});
    closeModal(); render();
  });

  render();
})();
</script>
</body>
</html>