Models 44

MiMo-V2.6-Flash

by Xiaomireasoning defaultClean
Open in new window

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

How it went

Total time
4 min 6 s
Cost
$0.0085
Output tokens
30,003
Spent thinking
16,738 tokens
Speed
123.7 tok/s
Console errors
0
First token after
3.1 s
First visible text after
2 min 55 s
Prompt tokens
496
Code size
42.6 KB
Finish reason
stop
Served by
Xiaomi
Model id
xiaomi/mimo-v2.6-flash
Reasoning sent
model default
Max output
131,072 tokens
Generated
Nov 13, 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 · 983 lines

<!DOCTYPE html>
<html lang="en" data-theme="light">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>The Live Queue — Kanban Board</title>
<style>
  :root{
    --canvas:#F4F1EA;
    --surface:#FFFDF8;
    --dark:#111315;
    --ink:#111315;
    --ink-inv:#FFFDF8;
    --muted:#68706F;
    --rule:#C9C5BC;
    --rule-strong:#111315;
    --voltage:#C6FF3D;
    --coral:#FF6B4A;
    --blue:#4777FF;
    --low:#68706F;
    --overlay:rgba(17,19,21,.52);
    --grid-line:rgba(17,19,21,.055);
    --tint:#EAF9C4;
    --header-fg:#FFFDF8;
    --header-dim:#9AA1A0;
    --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
    --display: "Arial Narrow", "Roboto Condensed", "Helvetica Neue Condensed", "Helvetica Neue", Arial, sans-serif;
    --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  }
  [data-theme="dark"]{
    --canvas:#111315;
    --surface:#191C1D;
    --ink:#FFFDF8;
    --ink-inv:#FFFDF8;
    --muted:#9AA1A0;
    --rule:#33383A;
    --rule-strong:#4A4F51;
    --overlay:rgba(0,0,0,.68);
    --grid-line:rgba(255,253,248,.06);
    --tint:#26310F;
    --low:#9AA1A0;
  }

  *{box-sizing:border-box}
  html,body{margin:0;padding:0}
  html{-webkit-text-size-adjust:100%}
  body{
    min-height:100vh;
    display:flex;
    flex-direction:column;
    background-color:var(--canvas);
    background-image:
      linear-gradient(to right, var(--grid-line) 1px, transparent 1px),
      linear-gradient(to bottom, var(--grid-line) 1px, transparent 1px);
    background-size:32px 32px;
    color:var(--ink);
    font-family:var(--sans);
    overflow-x:hidden;
  }
  .wrap{width:100%;max-width:1440px;margin:0 auto;padding:0 32px}
  .mono{font-family:var(--mono)}
  .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
  :focus-visible{outline:3px solid var(--voltage);outline-offset:2px}
  button{font-family:var(--mono)}

  .skip{
    position:absolute;left:8px;top:-60px;z-index:200;background:var(--voltage);color:#111315;
    padding:10px 14px;font:600 12px/1 var(--mono);letter-spacing:.06em;text-decoration:none;border:1px solid #111315;
  }
  .skip:focus{top:8px}

  /* ---------- HEADER ---------- */
  .topbar{
    position:sticky;top:0;z-index:60;
    background:var(--dark);color:var(--header-fg);
    border-bottom:1px solid rgba(255,253,248,.18);
  }
  .topbar-inner{display:flex;flex-wrap:wrap;align-items:center;gap:12px 22px;min-height:72px;padding-top:10px;padding-bottom:10px}
  .brand{order:1}
  .mark{
    display:inline-block;font-family:var(--mono);font-weight:600;font-size:14px;letter-spacing:.08em;
    background:var(--voltage);color:#111315;padding:7px 9px;
  }
  .bar-meta{order:2;margin-right:auto;display:flex;align-items:center;gap:14px;font:600 11px/1 var(--mono);letter-spacing:.1em;color:var(--header-dim)}
  .bar-meta .dot{width:6px;height:6px;background:var(--voltage);display:inline-block}
  .bar-meta strong{color:var(--header-fg);font-weight:600}
  .bar-actions{order:3;margin-left:auto;display:flex;align-items:center;gap:10px}

  .btn{
    display:inline-flex;align-items:center;justify-content:center;gap:8px;
    font:600 12px/1 var(--mono);letter-spacing:.08em;text-transform:uppercase;
    padding:13px 16px;border:1px solid var(--ink);background:transparent;color:var(--ink);
    cursor:pointer;transition:background 140ms ease,color 140ms ease,border-color 140ms ease;
  }
  .btn:hover{background:var(--ink);color:var(--canvas)}
  .btn-primary{background:var(--voltage);border-color:#111315;color:#111315}
  .btn-primary:hover{background:#111315;color:var(--voltage);border-color:var(--voltage)}
  .topbar .btn{border-color:rgba(255,253,248,.45);color:var(--header-fg);padding:11px 14px}
  .topbar .btn:hover{background:var(--header-fg);color:#111315;border-color:var(--header-fg)}
  .topbar .btn-primary{background:var(--voltage);border-color:var(--voltage);color:#111315}
  .topbar .btn-primary:hover{background:#111315;color:var(--voltage);border-color:var(--voltage)}

  /* ---------- DECK ---------- */
  main{flex:1 0 auto}
  .deck{padding:44px 0 26px}
  .deck-top{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:40px;align-items:end}
  .eyebrow{
    font:600 11px/1.3 var(--mono);letter-spacing:.18em;color:var(--muted);
    margin:0 0 14px;text-transform:uppercase;
  }
  .display-title{
    font-family:var(--display);font-stretch:condensed;font-weight:700;
    font-size:clamp(56px,8vw,128px);line-height:.86;letter-spacing:-.025em;
    margin:0 0 16px;margin-left:-5px;text-transform:uppercase;color:var(--ink);
  }
  .lede{margin:0;max-width:44ch;font-size:16px;line-height:1.5;color:var(--muted)}
  .deck-tools{display:flex;flex-direction:column;gap:12px;align-items:flex-end;min-width:min(100%,320px)}
  .search{
    position:relative;display:flex;align-items:center;width:100%;min-width:240px;max-width:420px;
    background:var(--surface);border:1px solid var(--ink);
  }
  .search svg{position:absolute;left:13px;color:var(--muted);pointer-events:none}
  .search input{
    width:100%;padding:13px 40px 13px 38px;border:0;background:transparent;color:var(--ink);
    font:400 14px/1.2 var(--mono);outline:none;
  }
  .search input::placeholder{color:var(--muted)}
  .search:focus-within{outline:3px solid var(--voltage);outline-offset:2px}
  .search .clear{
    position:absolute;right:6px;width:26px;height:26px;display:none;align-items:center;justify-content:center;
    border:0;background:transparent;color:var(--muted);cursor:pointer
  }
  .search.has-value .clear{display:flex}
  .resultline{font:600 11px/1.4 var(--mono);letter-spacing:.1em;color:var(--muted);text-align:right}
  .resultline b{color:var(--ink);font-weight:600}
  .theme-btn{
    display:inline-flex;align-items:center;gap:9px;padding:11px 13px;border:1px solid var(--ink);
    background:transparent;color:var(--ink);font:600 11px/1 var(--mono);letter-spacing:.1em;cursor:pointer;
    transition:background 140ms ease,color 140ms ease;
  }
  .theme-btn:hover{background:var(--ink);color:var(--canvas)}

  .strip{
    display:flex;flex-wrap:wrap;gap:0 28px;align-items:center;
    border-top:2px solid var(--rule-strong);border-bottom:1px solid var(--rule-strong);
    margin-top:34px;padding:12px 0;
    font:600 11px/1 var(--mono);letter-spacing:.14em;color:var(--muted);
  }
  .strip span{display:inline-flex;align-items:center;gap:8px}
  .strip b{color:var(--ink);font-weight:600}
  .strip .live i{width:7px;height:7px;background:var(--voltage);border:1px solid var(--rule-strong);display:inline-block;animation:blink 2s steps(1,end) infinite}
  @keyframes blink{0%,60%{opacity:1}61%,100%{opacity:.25}}

  /* ---------- BOARD ---------- */
  .board-shell{padding-bottom:48px}
  .board{
    display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:24px;
    max-width:100%;
  }
  .column{
    position:relative;display:flex;flex-direction:column;min-height:560px;
    padding-top:18px;
    transition:outline-color 140ms ease;
    outline:2px solid transparent;outline-offset:8px;
  }
  .column + .column::before{
    content:"";position:absolute;left:-13px;top:0;bottom:0;width:2px;background:var(--rule-strong);
  }
  .column.drag-over{outline-color:var(--voltage)}
  .col-head{
    position:relative;display:flex;align-items:flex-start;justify-content:space-between;gap:12px;
    border-bottom:2px solid var(--rule-strong);padding-bottom:12px;
  }
  .caption{margin:0;font:600 11px/1 var(--mono);letter-spacing:.16em;color:var(--muted);text-transform:uppercase}
  .count{
    margin:8px 0 0;font-family:var(--mono);font-weight:600;font-size:32px;line-height:1;letter-spacing:-.02em;color:var(--ink);
  }
  .add-inline{
    width:34px;height:34px;display:flex;align-items:center;justify-content:center;
    border:1px solid var(--ink);background:transparent;color:var(--ink);cursor:pointer;
    opacity:0;transition:opacity 140ms ease,background 140ms ease,color 140ms ease;
  }
  .column:hover .add-inline,.add-inline:focus-visible{opacity:1}
  .add-inline:hover{background:var(--voltage);border-color:var(--ink);color:#111315}
  .drop-hint{
    display:none;position:absolute;right:0;top:-4px;
    background:var(--ink);color:var(--voltage);padding:5px 7px;
    font:600 10px/1 var(--mono);letter-spacing:.1em;
  }
  .column.drag-over .drop-hint{display:block}

  .stack{
    position:relative;display:flex;flex-direction:column;gap:12px;
    padding-top:24px;min-height:340px;flex:1;
  }

  /* ---------- CARD ---------- */
  .card{
    position:relative;display:flex;flex-direction:column;justify-content:space-between;gap:18px;
    min-height:148px;padding:20px 20px 16px;
    background:var(--surface);
    border:1px solid var(--ink);
    border-left:5px solid var(--edge,var(--muted));
    cursor:grab;
    transition:transform 140ms ease,background 220ms ease,border-left-color 220ms ease,opacity 140ms ease;
    -webkit-user-select:none;user-select:none;
  }
  .stack > article:nth-of-type(2){margin-left:12px}
  .stack > article:nth-of-type(4){margin-left:8px}
  .card:hover{transform:translateY(-3px)}
  .card:focus-visible{outline:3px solid var(--voltage);outline-offset:3px}
  .card.dragging{opacity:.38;transform:scale(1.02);cursor:grabbing}
  .card.picked{outline:3px solid var(--voltage);outline-offset:4px}
  .card.touched{background:var(--tint);border-left-color:var(--voltage);border-left-width:7px;padding-left:18px}
  .card.enter{animation:cardIn 360ms cubic-bezier(.16,1,.3,1)}
  @keyframes cardIn{from{opacity:0;transform:translateY(-18px) scale(.98)}to{opacity:1;transform:none}}

  .badge{
    display:inline-flex;align-items:center;gap:7px;align-self:flex-start;
    padding:5px 7px;border:1px solid currentColor;
    font:600 10px/1 var(--mono);letter-spacing:.12em;text-transform:uppercase;
  }
  .badge i{width:8px;height:8px;background:currentColor;display:block}
  .badge.high{color:var(--coral);background:rgba(255,107,74,.13);border-color:rgba(255,107,74,.55)}
  .badge.medium{color:var(--blue);background:rgba(71,119,255,.12);border-color:rgba(71,119,255,.5)}
  .badge.low{color:var(--low);background:rgba(104,112,111,.12);border-color:rgba(104,112,111,.5)}
  [data-theme="dark"] .badge.medium{color:#7FA0FF}
  [data-theme="dark"] .badge.high{color:#FF8A6E}

  .card-title{
    font-family:var(--display);font-stretch:condensed;font-weight:700;
    font-size:24px;line-height:1.06;letter-spacing:-.01em;margin:14px 0 0;color:var(--ink);
    overflow-wrap:anywhere;
  }
  mark.hit{background:transparent;color:inherit;font-weight:700;position:relative}
  mark.hit::after{
    content:"";position:absolute;left:0;right:0;bottom:-3px;height:2px;background:var(--voltage);
    transform-origin:left;animation:hitline 900ms cubic-bezier(.22,1,.36,1) forwards;
  }
  @keyframes hitline{0%{transform:scaleX(0)}42%{transform:scaleX(1)}74%{transform:scaleX(1)}100%{transform:scaleX(0)}}

  .card-foot{display:flex;align-items:center;gap:10px;border-top:1px solid var(--rule);padding-top:12px}
  .card-foot .meta{font:600 11px/1 var(--mono);letter-spacing:.06em;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .grip{margin-left:auto;color:var(--rule);display:flex;opacity:.55;transition:color 140ms ease,opacity 140ms ease}
  .card:hover .grip{color:var(--ink);opacity:1}
  .del{
    width:30px;height:30px;display:flex;align-items:center;justify-content:center;flex:none;
    border:1px solid var(--rule);background:transparent;color:var(--muted);cursor:pointer;
    transition:background 140ms ease,color 140ms ease,border-color 140ms ease;
  }
  .del:hover{background:var(--coral);border-color:#111315;color:#111315}
  .pick-hint{
    display:none;margin-top:12px;padding:6px 8px;background:var(--ink);color:var(--voltage);
    font:600 10px/1.4 var(--mono);letter-spacing:.08em;
  }
  .card.picked .pick-hint{display:block}

  .empty{display:flex;flex-direction:column;gap:12px;padding-top:4px}
  .rail-empty{height:0;border-top:2px dashed var(--rule);margin:14px 0 6px}
  .empty p{margin:0;font-family:var(--display);font-stretch:condensed;font-weight:700;font-size:22px;line-height:1.1;color:var(--ink)}
  .empty span{font:600 11px/1.5 var(--mono);letter-spacing:.08em;color:var(--muted)}

  .sweep{
    position:absolute;left:0;right:0;top:14px;height:1px;background:var(--voltage);
    transform-origin:left;pointer-events:none;z-index:8;
    animation:sweep 420ms cubic-bezier(.22,1,.36,1) forwards;
  }
  @keyframes sweep{0%{transform:scaleX(0);opacity:1}70%{transform:scaleX(1);opacity:1}100%{transform:scaleX(1);opacity:0}}

  .rail{
    position:relative;height:2px;background:var(--voltage);transform-origin:left;
    animation:railIn 180ms cubic-bezier(.22,1,.36,1) forwards;flex:none;
  }
  .rail::after{
    content:"DROP HERE";position:absolute;right:0;top:-17px;
    background:var(--ink);color:var(--voltage);padding:3px 5px;
    font:600 10px/1 var(--mono);letter-spacing:.1em;
  }
  @keyframes railIn{from{transform:scaleX(0)}to{transform:scaleX(1)}}

  /* ---------- FOOTER ---------- */
  .statusbar{background:var(--dark);color:var(--header-fg);border-top:1px solid rgba(255,253,248,.18);margin-top:auto}
  .status-inner{
    display:flex;flex-wrap:wrap;align-items:center;gap:10px 26px;min-height:48px;padding-top:12px;padding-bottom:12px;
    font:600 11px/1.4 var(--mono);letter-spacing:.12em;
  }
  .status-inner .lbl{color:var(--voltage)}
  .status-inner .dim{color:var(--header-dim);margin-left:auto}

  /* ---------- FAB ---------- */
  .fab{
    display:none;position:fixed;right:16px;bottom:16px;z-index:55;
    width:56px;height:56px;border-radius:50%;align-items:center;justify-content:center;
    background:var(--voltage);border:1px solid #111315;color:#111315;cursor:pointer;
  }

  /* ---------- MODAL ---------- */
  .scrim{
    position:fixed;inset:0;z-index:100;background:var(--overlay);
    display:flex;align-items:center;justify-content:center;padding:24px;
    opacity:0;transition:opacity 220ms cubic-bezier(.22,1,.36,1);
  }
  .scrim[hidden]{display:none}
  .scrim.open{opacity:1}
  .modal{
    width:100%;max-width:440px;background:var(--surface);border:2px solid var(--ink);
    transform:translateY(16px);transition:transform 220ms cubic-bezier(.22,1,.36,1);
    max-height:calc(100vh - 48px);overflow:auto;
  }
  .scrim.open .modal{transform:translateY(0)}
  .modal-head{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:18px 20px;border-bottom:2px solid var(--ink)}
  .modal-head h2{margin:0;font-family:var(--display);font-stretch:condensed;font-weight:700;font-size:28px;line-height:1;letter-spacing:-.01em}
  .icon-btn{
    width:34px;height:34px;display:flex;align-items:center;justify-content:center;
    border:1px solid var(--ink);background:transparent;color:var(--ink);cursor:pointer;transition:background 140ms ease,color 140ms ease;
  }
  .icon-btn:hover{background:var(--ink);color:var(--surface)}
  .modal form{padding:20px}
  .field{margin-bottom:20px}
  .field > label,.field .lab{
    display:block;font:600 11px/1 var(--mono);letter-spacing:.14em;text-transform:uppercase;color:var(--muted);margin-bottom:9px;
  }
  .field .lab span{color:var(--coral)}
  .field input[type="text"]{
    width:100%;padding:13px 14px;background:var(--canvas);border:1px solid var(--ink);color:var(--ink);
    font:400 16px/1.3 var(--mono);
  }
  .field input[type="text"]:focus-visible{outline:3px solid var(--voltage);outline-offset:2px}
  .field input[aria-invalid="true"]{border-color:var(--coral);border-width:2px}
  .err{display:flex;align-items:center;gap:7px;margin:9px 0 0;color:var(--coral);font:600 12px/1.4 var(--mono)}
  .err[hidden]{display:none}
  .seg{display:flex}
  .seg label{position:relative;display:block;flex:1}
  .seg input{position:absolute;opacity:0;width:1px;height:1px;margin:0}
  .seg span{
    display:block;padding:11px 8px;margin-left:-1px;text-align:center;cursor:pointer;
    border:1px solid var(--rule-strong);background:transparent;color:var(--ink);
    font:600 12px/1 var(--mono);letter-spacing:.08em;transition:background 140ms ease,color 140ms ease;
  }
  .seg label:first-child span{margin-left:0}
  .seg input:checked + span{background:var(--c);color:#111315;border-color:var(--ink);position:relative;z-index:1}
  .seg input:focus-visible + span{outline:3px solid var(--voltage);outline-offset:2px;z-index:2;position:relative}
  .seg span:hover{background:var(--rule)}
  .seg input:checked + span:hover{background:var(--c)}
  .modal-actions{display:flex;gap:12px;justify-content:flex-end;border-top:1px solid var(--rule);padding-top:18px}
  .dest{font:600 11px/1 var(--mono);letter-spacing:.1em;color:var(--muted);margin:0 0 18px}

  /* ---------- RESPONSIVE ---------- */
  @media (max-width:1100px){
    .wrap{padding:0 24px}
    .deck-top{gap:24px}
    .board{gap:16px}
    .column + .column::before{left:-9px}
  }
  @media (max-width:860px){
    .deck-top{grid-template-columns:minmax(0,1fr);align-items:start}
    .deck-tools{align-items:stretch;min-width:0}
    .search{max-width:none}
    .resultline{text-align:left}
  }
  @media (max-width:759px){
    .wrap{padding:0 16px}
    .topbar-inner{min-height:64px}
    .brand{order:1}
    .bar-meta{order:2;margin-left:auto;margin-right:0}
    .bar-actions{order:3;width:100%;justify-content:flex-end;margin-left:0}
    .btn-add-header{display:none}
    .fab{display:flex}
    .deck{padding:32px 0 20px}
    .strip{gap:0 18px;margin-top:26px}
    .board{
      display:flex;overflow-x:auto;gap:16px;padding-bottom:10px;
      scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch;
    }
    .column{flex:0 0 calc(100% - 40px);min-height:430px;scroll-snap-align:start}
    .column + .column::before{display:none}
    .add-inline{opacity:1}
    .card{cursor:pointer}
    .grip{display:none}
    .status-inner .dim{margin-left:0}
    .statusbar{padding-bottom:76px}
  }
  @media (max-width:400px){
    .display-title{font-size:52px}
    .bar-meta{font-size:10px;gap:10px}
  }

  @media (prefers-reduced-motion:reduce){
    *,*::before,*::after{animation-duration:.01ms !important;animation-iteration-count:1 !important;transition-duration:.01ms !important}
    .card:hover{transform:none}
    .sweep{opacity:1;transform:scaleX(1)}
    .rail{transform:scaleX(1)}
  }
</style>
</head>
<body>
<a class="skip" href="#board">Skip to board</a>

<header class="topbar">
  <div class="wrap topbar-inner">
    <div class="brand"><span class="mark">KANBAN//03</span></div>
    <div class="bar-meta">
      <span id="dateLabel">TUE 14 MAY</span>
      <span class="dot" aria-hidden="true"></span>
      <strong id="headerCount">06 CARDS</strong>
    </div>
    <div class="bar-actions">
      <button class="btn" id="themeBtnTop" type="button" aria-pressed="false">
        <span class="theme-icon" aria-hidden="true"></span>
        <span class="theme-label">DARK</span>
      </button>
      <button class="btn btn-primary btn-add-header" id="addBtnTop" type="button">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>
        Add card
      </button>
    </div>
  </div>
</header>

<main>
  <section class="wrap deck">
    <div class="deck-top">
      <div>
        <p class="eyebrow">Board 01 / Personal operations</p>
        <h1 class="display-title">Work<br>Queue</h1>
        <p class="lede">Move the work. Keep the signal. Three rails, six live tickets — drag, filter, or pick a card up with the keyboard.</p>
      </div>
      <div class="deck-tools">
        <div class="search" id="searchWrap">
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true"><circle cx="11" cy="11" r="6.5"/><path d="M16 16l4.6 4.6"/></svg>
          <label class="sr-only" for="searchInput">Search cards by title</label>
          <input id="searchInput" type="text" placeholder="Search cards…" autocomplete="off" spellcheck="false" />
          <button class="clear" id="clearSearch" type="button" aria-label="Clear search">
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg>
          </button>
        </div>
        <p class="resultline" id="resultCount" aria-live="polite"><b>06</b> OF 06 CARDS MATCH</p>
        <button class="theme-btn" id="themeBtnDeck" type="button" aria-pressed="false">
          <span class="theme-icon" aria-hidden="true"></span>
          <span class="theme-label">DARK THEME</span>
        </button>
      </div>
    </div>

    <div class="strip">
      <span><b id="stripCards">06</b> CARDS</span>
      <span><b>03</b> COLUMNS</span>
      <span class="live"><i aria-hidden="true"></i> LIVE QUEUE</span>
      <span>DRAG · SEARCH · REORDER</span>
    </div>
  </section>

  <section class="wrap board-shell">
    <div class="board" id="board" aria-label="Kanban board"></div>
  </section>
</main>

<footer class="statusbar">
  <div class="wrap status-inner">
    <span class="lbl">QUEUE STATUS</span>
    <span id="footCounts">TO DO 03 / IN PROGRESS 02 / DONE 01</span>
    <span class="dim">IN-MEMORY SESSION · NO PERSISTENCE</span>
  </div>
</footer>

<button class="fab" id="fabAdd" type="button" aria-label="Add card" title="Add card">
  <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>
</button>

<div class="scrim" id="scrim" hidden>
  <div class="modal" role="dialog" aria-modal="true" aria-labelledby="modalTitle">
    <div class="modal-head">
      <h2 id="modalTitle">Add to the queue</h2>
      <button class="icon-btn" id="closeModal" type="button" aria-label="Close dialog">
        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg>
      </button>
    </div>
    <form id="cardForm" novalidate>
      <p class="dest" id="destLabel">DESTINATION · TO DO</p>
      <div class="field">
        <label class="lab" for="titleInput">Card title <span>*</span></label>
        <input id="titleInput" type="text" placeholder="e.g. Fix checkout validation error" autocomplete="off" aria-describedby="titleErr" />
        <p class="err" id="titleErr" hidden>
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M12 4l9 16H3L12 4z"/><path d="M12 10v4M12 17h.01"/></svg>
          Give this card a title before adding it.
        </p>
      </div>
      <div class="field">
        <span class="lab" id="prioLab">Priority</span>
        <div class="seg" role="group" aria-labelledby="prioLab">
          <label style="--c:#DAD6CC"><input type="radio" name="priority" value="low"><span>LOW</span></label>
          <label style="--c:#4777FF"><input type="radio" name="priority" value="medium" checked><span>MEDIUM</span></label>
          <label style="--c:#FF6B4A"><input type="radio" name="priority" value="high"><span>HIGH</span></label>
        </div>
      </div>
      <div class="modal-actions">
        <button class="btn" id="cancelBtn" type="button">Cancel</button>
        <button class="btn btn-primary" type="submit">Add card</button>
      </div>
    </form>
  </div>
</div>

<div class="sr-only" role="status" aria-live="polite" id="srLive"></div>

<script>
(function(){
  "use strict";

  /* ---------------- icons ---------------- */
  var ICON = {
    plus:'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>',
    trash:'<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true"><path d="M4 7h16M9.5 7V4.5h5V7M6.5 7l1 12.5h9L17.5 7M10 10.5v6M14 10.5v6"/></svg>',
    grip:'<svg width="16" height="16" viewBox="0 0 24 24" aria-hidden="true"><g fill="currentColor"><circle cx="9" cy="6" r="1.3"/><circle cx="9" cy="12" r="1.3"/><circle cx="9" cy="18" r="1.3"/><circle cx="15" cy="6" r="1.3"/><circle cx="15" cy="12" r="1.3"/><circle cx="15" cy="18" r="1.3"/></g></svg>',
    sun:'<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 2.5v2.5M12 19v2.5M2.5 12H5M19 12h2.5M5.2 5.2l1.8 1.8M17 17l1.8 1.8M18.8 5.2L17 7M7 17l-1.8 1.8"/></svg>',
    moon:'<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true"><path d="M20 14.5A8.2 8.2 0 0 1 9.5 4a8.5 8.5 0 1 0 10.5 10.5z"/></svg>'
  };

  var PRIO = {
    low:{label:'LOW / PRIORITY', color:'var(--low)'},
    medium:{label:'MEDIUM / PRIORITY', color:'var(--blue)'},
    high:{label:'HIGH / PRIORITY', color:'var(--coral)'}
  };

  /* ---------------- state ---------------- */
  var state = {
    theme:'light',
    query:'',
    pickedId:null,
    addTarget:'todo',
    flash:null,
    columns:[
      {id:'todo', name:'To do', cards:[
        {id:'TK-104', title:'Audit onboarding flow', priority:'high', time:'9m ago'},
        {id:'TK-105', title:'Prepare Q3 campaign brief', priority:'medium', time:'42m ago'},
        {id:'TK-106', title:'Review mobile empty states', priority:'low', time:'1h ago'}
      ]},
      {id:'progress', name:'In progress', cards:[
        {id:'TK-101', title:'Fix checkout validation error', priority:'high', time:'18m ago'},
        {id:'TK-102', title:'Write release notes for v2.4', priority:'medium', time:'2h ago'}
      ]},
      {id:'done', name:'Done', cards:[
        {id:'TK-097', title:'Publish support handoff checklist', priority:'low', time:'Yesterday'}
      ]}
    ]
  };
  var idSeq = 107;

  /* ---------------- helpers ---------------- */
  var $ = function(s){ return document.querySelector(s); };
  var board = $('#board');
  var srLive = $('#srLive');

  function pad(n){ return (n < 10 ? '0' : '') + n; }
  function esc(s){ return String(s).replace(/[&<>"']/g, function(c){
    return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]; }); }
  function announce(msg){ srLive.textContent = msg; }
  function colById(id){ for(var i=0;i<state.columns.length;i++){ if(state.columns[i].id===id) return state.columns[i]; } return null; }
  function findCard(id){
    for(var i=0;i<state.columns.length;i++){
      var c = state.columns[i], idx = c.cards.findIndex(function(x){ return x.id===id; });
      if(idx > -1) return {col:c, colIndex:i, index:idx, card:c.cards[idx]};
    }
    return null;
  }
  function total(){ return state.columns.reduce(function(n,c){ return n + c.cards.length; },0); }

  function highlight(title, q){
    if(!q) return esc(title);
    var lower = title.toLowerCase(), i = lower.indexOf(q);
    if(i < 0) return esc(title);
    return esc(title.slice(0,i)) + '<mark class="hit">' + esc(title.slice(i, i+q.length)) + '</mark>' + esc(title.slice(i+q.length));
  }

  /* ---------------- render ---------------- */
  function cardHtml(card, col, pos, count, q){
    var p = PRIO[card.priority];
    var picked = state.pickedId === card.id;
    var aria = card.title + '. Priority ' + card.priority + '. In ' + col.name + ', position ' + pos + ' of ' + count +
               '. Press space to pick up, arrow keys to move, escape to cancel.';
    return '<article class="card' + (picked ? ' picked' : '') + '" data-id="' + card.id + '" draggable="true" tabindex="0" ' +
      'role="listitem" aria-label="' + esc(aria) + '" style="--edge:' + p.color + '">' +
        '<div>' +
          '<span class="badge ' + card.priority + '"><i aria-hidden="true"></i>' + p.label + '</span>' +
          '<h3 class="card-title">' + highlight(card.title, q) + '</h3>' +
          '<span class="pick-hint">MOVING · ← → COLUMN · ↑ ↓ ORDER · SPACE DROP · ESC CANCEL</span>' +
        '</div>' +
        '<div class="card-foot">' +
          '<span class="meta">#' + card.id + ' · ' + esc(card.time) + '</span>' +
          '<span class="grip" title="Drag to move" aria-hidden="true">' + ICON.grip + '</span>' +
          '<button class="del" data-del="' + card.id + '" draggable="false" aria-label="Delete card: ' + esc(card.title) + '">' + ICON.trash + '</button>' +
        '</div>' +
      '</article>';
  }

  function emptyHtml(q){
    if(q) return '<div class="empty"><div class="rail-empty"></div><p>No matching cards.</p><span>TRY A DIFFERENT SEARCH.</span></div>';
    return '<div class="empty"><div class="rail-empty"></div><p>Nothing queued here.</p><span>DRAG A CARD HERE OR USE ADD CARD.</span></div>';
  }

  function columnHtml(col){
    var q = state.query.trim().toLowerCase();
    var visible = col.cards.filter(function(c){ return !q || c.title.toLowerCase().indexOf(q) > -1; });
    var body = visible.length
      ? visible.map(function(c,i){ return cardHtml(c, col, i+1, visible.length, q); }).join('')
      : emptyHtml(q);
    return '<section class="column" data-col="' + col.id + '" aria-labelledby="h-' + col.id + '">' +
      '<div class="col-head">' +
        '<div>' +
          '<p class="caption" id="h-' + col.id + '">' + col.name + '</p>' +
          '<p class="count">' + pad(visible.length) + '</p>' +
        '</div>' +
        '<button class="add-inline" data-add="' + col.id + '" aria-label="Add card to ' + col.name + '" title="Add card to ' + col.name + '">' + ICON.plus + '</button>' +
        '<span class="drop-hint">DROP CARDS HERE</span>' +
      '</div>' +
      '<div class="stack" role="list" aria-label="' + col.name + ' cards">' + body + '</div>' +
    '</section>';
  }

  var focusAfterRender = null;

  function render(){
    board.innerHTML = state.columns.map(columnHtml).join('');
    updateCounts();

    if(state.flash){
      var el = board.querySelector('[data-id="' + state.flash.id + '"]');
      if(el){
        if(state.flash.type === 'enter'){ el.classList.add('enter'); }
        else { sweep(state.flash.col, el); }
      }
      state.flash = null;
    }
    if(focusAfterRender){
      var target = board.querySelector('[data-id="' + focusAfterRender + '"]');
      if(target) target.focus();
      focusAfterRender = null;
    }
  }

  function updateCounts(){
    var t = total();
    $('#headerCount').textContent = pad(t) + ' CARDS';
    $('#stripCards').textContent = pad(t);
    $('#footCounts').textContent = state.columns.map(function(c){
      return c.name.toUpperCase() + ' ' + pad(c.cards.length);
    }).join(' / ');

    var q = state.query.trim().toLowerCase(), matches = 0;
    state.columns.forEach(function(c){
      c.cards.forEach(function(x){ if(!q || x.title.toLowerCase().indexOf(q) > -1) matches++; });
    });
    $('#resultCount').innerHTML = q
      ? '<b>' + pad(matches) + '</b> OF ' + pad(t) + ' CARDS MATCH'
      : '<b>' + pad(t) + '</b> CARDS IN QUEUE';
  }

  /* ---------------- motion: queue sweep ---------------- */
  function sweep(colId, cardEl){
    var col = board.querySelector('[data-col="' + colId + '"]');
    if(col){
      var line = document.createElement('div');
      line.className = 'sweep';
      col.appendChild(line);
      line.addEventListener('animationend', function(){ if(line.parentNode) line.parentNode.removeChild(line); });
      setTimeout(function(){ if(line.parentNode) line.parentNode.removeChild(line); }, 900);
    }
    if(cardEl){
      cardEl.classList.add('touched');
      setTimeout(function(){ cardEl.classList.remove('touched'); }, 720);
    }
  }
  function sweepForCard(el){
    var found = el.getAttribute('data-id');
    var f = findCard(found);
    if(f) sweep(f.col.id, el);
  }

  /* ---------------- model ops ---------------- */
  function moveCard(id, targetColId, index){
    var src = findCard(id);
    if(!src) return null;
    src.col.cards.splice(src.index, 1);
    var target = colById(targetColId);
    var i = Math.max(0, Math.min(index, target.cards.length));
    target.cards.splice(i, 0, src.card);
    return {col:target, index:i};
  }

  function removeCard(id){
    var f = findCard(id);
    if(!f) return;
    f.col.cards.splice(f.index, 1);
    if(state.pickedId === id) state.pickedId = null;
    render();
    announce('Card deleted: ' + f.card.title + '. ' + total() + ' cards remain.');
  }

  /* ---------------- search ---------------- */
  var searchInput = $('#searchInput');
  searchInput.addEventListener('input', function(){
    state.query = searchInput.value;
    $('#searchWrap').classList.toggle('has-value', !!state.query);
    render();
  });
  $('#clearSearch').addEventListener('click', function(){
    searchInput.value = ''; state.query = '';
    $('#searchWrap').classList.remove('has-value');
    render(); searchInput.focus();
  });

  /* ---------------- theme ---------------- */
  function setTheme(mode){
    state.theme = mode;
    document.documentElement.setAttribute('data-theme', mode);
    var dark = mode === 'dark';
    ['#themeBtnTop','#themeBtnDeck'].forEach(function(sel){
      var b = $(sel);
      b.setAttribute('aria-pressed', dark ? 'true' : 'false');
      b.querySelector('.theme-icon').innerHTML = dark ? ICON.sun : ICON.moon;
      b.querySelector('.theme-label').textContent = (sel === '#themeBtnDeck') ? (dark ? 'LIGHT THEME' : 'DARK THEME') : (dark ? 'LIGHT' : 'DARK');
    });
    announce(dark ? 'Dark theme on.' : 'Light theme on.');
  }
  $('#themeBtnTop').addEventListener('click', function(){ setTheme(state.theme === 'light' ? 'dark' : 'light'); });
  $('#themeBtnDeck').addEventListener('click', function(){ setTheme(state.theme === 'light' ? 'dark' : 'light'); });

  /* ---------------- modal ---------------- */
  var scrim = $('#scrim'), form = $('#cardForm'), titleInput = $('#titleInput'),
      titleErr = $('#titleErr'), lastFocus = null;

  function openModal(colId){
    state.addTarget = colId || 'todo';
    var col = colById(state.addTarget);
    $('#destLabel').textContent = 'DESTINATION · ' + col.name.toUpperCase();
    lastFocus = document.activeElement;
    scrim.hidden = false;
    requestAnimationFrame(function(){ scrim.classList.add('open'); });
    titleInput.value = '';
    titleInput.setAttribute('aria-invalid','false');
    titleErr.hidden = true;
    setTimeout(function(){ titleInput.focus(); }, 40);
    document.addEventListener('keydown', trapTab, true);
  }
  function closeModal(){
    scrim.classList.remove('open');
    document.removeEventListener('keydown', trapTab, true);
    setTimeout(function(){
      scrim.hidden = true;
      if(lastFocus && document.contains(lastFocus)) lastFocus.focus();
    }, 200);
  }
  function trapTab(e){
    if(e.key === 'Escape'){ e.preventDefault(); closeModal(); return; }
    if(e.key !== 'Tab') return;
    var f = scrim.querySelectorAll('button, input, [href], [tabindex]:not([tabindex="-1"])');
    if(!f.length) return;
    var first = f[0], last = f[f.length-1];
    if(e.shiftKey && document.activeElement === first){ e.preventDefault(); last.focus(); }
    else if(!e.shiftKey && document.activeElement === last){ e.preventDefault(); first.focus(); }
  }

  $('#addBtnTop').addEventListener('click', function(){ openModal('todo'); });
  $('#fabAdd').addEventListener('click', function(){ openModal('todo'); });
  $('#closeModal').addEventListener('click', closeModal);
  $('#cancelBtn').addEventListener('click', closeModal);
  scrim.addEventListener('mousedown', function(e){ if(e.target === scrim) closeModal(); });

  titleInput.addEventListener('input', function(){
    if(titleInput.value.trim()){
      titleErr.hidden = true;
      titleInput.setAttribute('aria-invalid','false');
    }
  });

  form.addEventListener('submit', function(e){
    e.preventDefault();
    var val = titleInput.value.trim();
    if(!val){
      titleErr.hidden = false;
      titleInput.setAttribute('aria-invalid','true');
      titleInput.focus();
      announce('Error: give this card a title before adding it.');
      return;
    }
    var prio = form.querySelector('input[name="priority"]:checked').value;
    var col = colById(state.addTarget);
    var id = 'TK-' + (idSeq++);
    col.cards.unshift({id:id, title:val, priority:prio, time:'Just now'});
    closeModal();
    state.flash = {type:'enter', id:id};
    render();
    announce('Card added to ' + col.name + ': ' + val);
    var el = board.querySelector('[data-id="' + id + '"]');
    if(el) setTimeout(function(){ el.focus(); }, 380);
  });

  /* ---------------- drag & drop ---------------- */
  var dragId = null, rail = null, justDragged = false, dropTarget = null;

  function makeRail(){
    if(!rail){
      rail = document.createElement('div');
      rail.className = 'rail';
      rail.setAttribute('aria-hidden','true');
    }
    return rail;
  }
  function clearRail(){ if(rail && rail.parentNode) rail.parentNode.removeChild(rail); }
  function clearDragState(){
    clearRail();
    Array.prototype.forEach.call(board.querySelectorAll('.column'), function(c){ c.classList.remove('drag-over'); });
    var d = board.querySelector('.card.dragging'); if(d) d.classList.remove('dragging');
    dragId = null; dropTarget = null;
  }

  function computeIndex(stack, clientY){
    var all = Array.prototype.slice.call(stack.querySelectorAll('.card'));
    var idx = all.length;
    for(var i=0;i<all.length;i++){
      var r = all[i].getBoundingClientRect();
      if(clientY < r.top + r.height/2){ idx = i; break; }
    }
    var dragPos = -1;
    for(var j=0;j<all.length;j++){ if(all[j].classList.contains('dragging')) dragPos = j; }
    var filtered = all.filter(function(c){ return !c.classList.contains('dragging'); });
    var fidx = idx;
    if(dragPos > -1 && idx > dragPos) fidx = idx - 1;
    return {index: Math.max(0, Math.min(fidx, filtered.length)), ref: filtered[fidx] || null};
  }

  board.addEventListener('dragstart', function(e){
    var card = e.target.closest ? e.target.closest('.card') : null;
    if(!card) return;
    dragId = card.getAttribute('data-id');
    try{ e.dataTransfer.setData('text/plain', dragId); e.dataTransfer.effectAllowed = 'move'; }catch(err){}
    card.classList.add('dragging');
    var col = card.closest('.column'); if(col) col.classList.add('drag-over');
    announce('Dragging card.');
  });

  board.addEventListener('dragend', function(){
    justDragged = true;
    setTimeout(function(){ justDragged = false; }, 120);
    clearDragState();
  });

  board.addEventListener('dragover', function(e){
    if(!dragId) return;
    var stack = e.target.closest ? e.target.closest('.stack') : null;
    if(!stack) return;
    e.preventDefault();
    e.dataTransfer.dropEffect = 'move';
    var col = stack.closest('.column');
    Array.prototype.forEach.call(board.querySelectorAll('.column'), function(c){ c.classList.toggle('drag-over', c === col); });
    var res = computeIndex(stack, e.clientY);
    stack.insertBefore(makeRail(), res.ref);
    dropTarget = {col: col.getAttribute('data-col'), index: res.index};
  });

  board.addEventListener('dragleave', function(e){
    var stack = e.target.closest ? e.target.closest('.stack') : null;
    if(stack && (!e.relatedTarget || !stack.contains(e.relatedTarget))) clearRail();
  });

  board.addEventListener('drop', function(e){
    if(!dragId) return;
    var stack = e.target.closest ? e.target.closest('.stack') : null;
    if(!stack) return;
    e.preventDefault();
    var id = dragId;
    var target = dropTarget || {col: stack.closest('.column').getAttribute('data-col'), index: 0};
    var moved = moveCard(id, target.col, target.index);
    clearDragState();
    justDragged = true;
    setTimeout(function(){ justDragged = false; }, 150);
    if(moved){
      state.flash = {type:'sweep', id:id, col:moved.col.id};
      render();
      var pos = moved.col.cards.findIndex(function(c){ return c.id === id; }) + 1;
      announce('Card moved to ' + moved.col.name + ', position ' + pos + '.');
    }else{
      render();
    }
  });

  /* ---------------- clicks ---------------- */
  board.addEventListener('click', function(e){
    var del = e.target.closest('[data-del]');
    if(del){ e.stopPropagation(); removeCard(del.getAttribute('data-del')); return; }
    var add = e.target.closest('[data-add]');
    if(add){ openModal(add.getAttribute('data-add')); return; }
    if(justDragged) return;
    var card = e.target.closest('.card');
    if(card){ card.focus(); sweepForCard(card); }
  });

  /* ---------------- keyboard drag ---------------- */
  board.addEventListener('keydown', function(e){
    var card = e.target.closest ? e.target.closest('.card') : null;
    if(!card) return;
    var id = card.getAttribute('data-id');
    var f = findCard(id);
    if(!f) return;
    var key = e.key;

    if(key === ' ' || key === 'Spacebar' || key === 'Enter'){
      e.preventDefault();
      if(state.pickedId !== id){
        state.pickedId = id;
        state.pickedOrigin = {col:f.col.id, index:f.index};
        render();
        focusAfterRender = id; render();
        announce('Picked up ' + f.card.title + ' in ' + f.col.name + '. Use arrow keys to move, space to drop, escape to cancel.');
      }else{
        state.pickedId = null;
        state.pickedOrigin = null;
        state.flash = {type:'sweep', id:id, col:f.col.id};
        focusAfterRender = id;
        render();
        announce('Card dropped in ' + f.col.name + ', position ' + (f.index+1) + '.');
      }
      return;
    }

    if(key === 'Escape' && state.pickedId === id){
      e.preventDefault();
      var o = state.pickedOrigin;
      state.pickedId = null; state.pickedOrigin = null;
      if(o) moveCard(id, o.col, o.index);
      focusAfterRender = id;
      render();
      announce('Move cancelled.');
      return;
    }

    if(state.pickedId !== id) return;

    var handled = true, targetCol = f.colIndex, targetIndex = f.index;
    if(key === 'ArrowLeft'){ if(f.colIndex > 0){ targetCol = f.colIndex - 1; targetIndex = 0; } else handled = false; }
    else if(key === 'ArrowRight'){ if(f.colIndex < state.columns.length - 1){ targetCol = f.colIndex + 1; targetIndex = 0; } else handled = false; }
    else if(key === 'ArrowUp'){ if(f.index > 0){ targetIndex = f.index - 1; } else handled = false; }
    else if(key === 'ArrowDown'){ if(f.index < f.col.cards.length - 1){ targetIndex = f.index + 1; } else handled = false; }
    else handled = false;

    if(!handled) return;
    e.preventDefault();
    var dest = state.columns[targetCol];
    moveCard(id, dest.id, targetIndex);
    focusAfterRender = id;
    render();
    var nf = findCard(id);
    announce('Moved to ' + dest.name + ', position ' + (nf.index+1) + '.');
  });

  /* ---------------- global keys ---------------- */
  document.addEventListener('keydown', function(e){
    if(!scrim.hidden) return;
    if(e.key === 'Escape' && state.pickedId){
      var o = state.pickedOrigin, id = state.pickedId;
      state.pickedId = null; state.pickedOrigin = null;
      if(o) moveCard(id, o.col, o.index);
      focusAfterRender = id;
      render();
      announce('Move cancelled.');
    }
  });

  /* ---------------- init ---------------- */
  (function initDate(){
    try{
      var d = new Date();
      $('#dateLabel').textContent = d.toLocaleDateString('en-GB',{weekday:'short',day:'2-digit',month:'short'})
        .toUpperCase().replace(/,/g,'');
    }catch(err){ $('#dateLabel').textContent = 'TODAY'; }
  })();

  setTheme('light');
  render();
})();
</script>
</body>
</html>
Reasoning trace (16,738 tokens)
Let me build an interactive Kanban board with vanilla HTML, CSS, and JavaScript.

**Who this is for, and what it has to feel like.** This is a working tool, not a landing page. The person opening it wants to move work around immediately. So the whole design has to read as a *board you can touch*: heavy physical slips pinned to three rails, with instrumentation around them. The trap here is that "Kanban" pulls designers straight into grey SaaS dashboards with soft shadows and rounded pills — forgettable, and it makes the cards feel weightless. I'm going the other way: an editorial-technical control surface. Black graph-paper rules, square frames, one oversized condensed headline, and cards that look like stock-ticker tape — cream stock, black border, one fluorescent status line running down the left edge. The first screenshot should read as *a system in motion*.

**Palette.** Warm paper canvas at `#F4F1EA` — never white, white is a browser default and reads as unfinished. Cards sit above it in `#FFFDF8`, just enough separation to lift without shadow. The utility bar and footer go to `#111315`, a near-black with a green cast so it sits with the warm paper instead of fighting it; that same ink handles all text and every structural rule. Metadata drops to `#68706F`, inactive rules to `#C9C5BC`. Then the one loud note: `#C6FF3D`, fluorescent lime. It has to stay *operational* — it fires on the add button, on focus, on the drop rail, on the light sweep, and on the top-priority edge. Nowhere else. If lime shows up as a background field the whole thing turns into a sports brand. Urgency gets `#FF6B4A` coral for high priority only, `#4777FF` blue for medium, `#68706F` for low. Scrim is `rgba(17,19,21,0.52)`.

**Typography.** Two families, no more. Barlow Condensed carries display and card titles — condensed grotesk has the newspaper-headline compression I want, and it lets a card title wrap in two tight lines inside a 300px card. IBM Plex Mono carries every piece of metadata, every label, every button: counters, timestamps, column captions. That split is the whole voice — big editorial statements, small technical annotations. Nothing decorative in between. Wordmark at `clamp(56px,8vw,128px)`/0.86/700 is deliberately out of scale with everything else: one graphic interruption, then the board takes over.

**Layout system.** Three architectural bands: a `72px` dark control header (sticky), a command deck, then the board as the center of gravity, then a `48px` dark status footer. Content caps at `1440px` with `32px` gutters. The deck is asymmetric on purpose — headline block and count data on the left, search and theme on the right — and I break the grid once: the huge `WORK QUEUE` title runs past its column into the utility zone so the type feels like it doesn't respect the boxes. The board itself is `repeat(3,minmax(0,1fr))` with `24px` gaps, structural `2px` vertical rules between columns that run the full board height. Columns are `min-height:560px`, never scroll internally — all three must be visible at once on desktop.

The grid break I care most about: the **cards do not obey the card grid**. First card in each column is flush; the rest alternate `0 / 12 / 0 / 8px` horizontal offsets, a subtle pinboard irregularity. Rigid rails, loose slips.

**Motion.** Functional-micro. Nothing animates that would delay a click. The signature is the **Queue Sweep**: on a completed drop, a 1px lime line travels the full board width across the destination column's top edge while the target card takes a lime tint and its left edge thickens to 6px. 420ms, `cubic-bezier(.22,1,.36,1)`. Because a normal drag has no click to key off, I'll run the sweep automatically 180ms after drop as a confirmation — but the click-triggered version fires first when a card is clicked, so both requirements are satisfied by the same mechanism. Everything else is short: card entry `360ms` from the *top* of the destination stack (never from pointer position), buttons `140ms`, modal `220ms`, and a lime underline that retracts under the matching letters on each search keystroke — confirming the filter, not just highlighting it.

**Section by section.** The header is a dark band with a lime `KANBAN//03` mark, live date, live count, theme toggle, and the one filled lime add button. The deck gives the display title, a data strip (`06 CARDS`, `03 COLUMNS`, `LIVE QUEUE`), then `240px`-min search and theme control at right. Each column: mono caption, big count at `32px`, add-plus that only appears on hover or keyboard focus, and a full-height dashed empty rail that draws itself when a column empties. Cards: `5px` priority edge, mono `HIGH / PRIORITY` label with an 8px square, wrapped title, and a footer of `#TASK-ID · time · grip handle` with the delete button at the right. Drag affordances are progressive — handle only at rest, whole card during drag, destination gets a `2px` lime outline and a top rail labeled `DROP HERE`. Keyboard parity matters: focus a card, Space to pick up, arrows to move, Space to drop, Escape to cancel, announced through a visually-hidden live region. Footer echoes the current column counts.

Now, the build.

Key elements:
- Dark header 72px, wordmark KANBAN//03 in lime, date, card count, theme toggle, Add card button.
- Command deck: eyebrow BOARD01 / PERSONAL OPERATIONS, title WORK QUEUE (display), supporting line, utility strip 06 CARDS / 03 COLUMNS / LIVE QUEUE, search field, theme toggle right.
- Board: 3 columns with header (label, count, + button), cards.
- Cards: 5px left edge, priority label, title, footer (id, time, grip handle, delete button).
- Drag/drop HTML5, drop indicator, queue sweep on drop.
- Modal with form, validation, focus trap, Escape.
- Search filtering live, aria-live result count.
- Theme toggle light/dark.
- Footer dark status bar with counts.
- Keyboard: Space pick up, arrows move, Space drop, Escape cancel.
- Reduced motion.
- Mobile stack.

Let me write the code carefully.

Colors CSS vars with [data-theme="dark"] overrides.

Layout: header sticky top. Then main with padding.

Display title "WORK QUEUE" clamp(56px,8vw,128px) — that's huge; on the deck left column. Utility strip below.

Let me structure:

```html
<header class="topbar">
  <div class="bar-inner">
    <div class="mark">KANBAN<span>//03</span></div>
    <div class="meta">TUE 14 MAY</div>
    <div class="meta count">06 CARDS</div>
    <div class="actions">
      <button class="theme-toggle" aria-pressed="false">icon + label</button>
      <button class="btn-add">+ ADD CARD</button>
    </div>
  </div>
</header>
```

Wait — there are two theme toggles mentioned (header and command deck). I'll put one in the deck (control) and one in the header — but two toggles must stay in sync. Simpler: put the theme toggle in the command deck with label, and header has icon-only toggle. Both sync via shared function. Actually simpler to just have one in header... but having theme toggle in both keeps consistency across layouts. I'll do both with sync.

Board grid with structural vertical rules: use border-right on columns except last, or use gap with pseudo-elements. Simplest: column has border-left on 2nd/3rd? Use `border-inline-start: 2px solid var(--rule)` on columns not first, with padding-inline-start. Actually with gap 24px, the rule would be at the left edge of columns 2 and 3. Fine — use box-shadow inset? I'll use a pseudo element positioned at the gap center: `.column:not(:first-child)::before { content:''; position:absolute; left:-13px; top:0; bottom:0; width:2px; background:var(--rule-strong) }`. Columns need position:relative. That runs full height of board (column heights equal via grid stretch). Good.

Header row of column: border-bottom 2px.

Cards stack: `.stack { display:flex; flex-direction:column; gap:12px; padding-top:24px; min-height: 300px }`.

Empty state rail: dashed border spanning column, with text. Show when column has no visible cards.

Drop indicator: when dragging over column, show `::after` line at insertion point. Simpler approach: a dedicated element `.drop-rail` inserted into the DOM in the stack at the computed index, showing 2px lime line + DROP HERE label. I'll implement: on dragover of a stack, compute index based on mouse Y relative to card midpoints, then move/insert a `#dropRail` element. Plus column outline class.

Let's implement drag:

```js
let dragId = null;
card.addEventListener('dragstart', e => { dragId = card.dataset.id; e.dataTransfer.setData('text/plain', dragId); e.dataTransfer.effectAllowed='move'; setTimeout(()=>card.classList.add('dragging'),0) })
```

dragover on stack: e.preventDefault(); compute index; insert rail.
drop: move card in data model, re-render, run queue sweep.

Rendering: full re-render of columns on state change is simplest, but drag with re-render is fine since drag ends. But careful: re-render during dragover would break. I'll do DOM move on drop instead: move the card element to the target position, then update data model, then update counts. Actually simpler: on drop, update model and re-render, then find new card element and animate entry + sweep.

For search filter, re-render with filtering.

Card entry animation: add class `card-enter` to newly added card after render.

Queue sweep: element `.sweep` inside board at target column top; animate a1px lime line traveling horizontally across the target column's top edge over 420ms. Implement as a div positioned at the top of the target column, width 0 -> 100% via transform scaleX, plus a card lime tint. I'll create an absolutely positioned div inside the column header area: `.sweep { position:absolute; left:0; right:0; top:0; height:1px; background:var(--voltage); transform-origin:left; transform:scaleX(0); animation: sweep 420ms cubic-bezier(.22,1,.36,1) }` and remove after animation. Also card gets `.sweep-tint` class for 420ms.

Card target lime edge: card left edge becomes lime briefly.

Keyboard: card is a div with tabindex=0, role="button"? Let's use `role="listitem"`? For accessibility: each stack is `role="list"`, cards `role="listitem"` with tabindex=0 and aria-label describing. Keyboard drag: Space toggles pickup mode (aria-grabbed true), arrow left/right moves column, arrow up/down reorders within column, Space drop, Escape cancel. I'll implement a simplified but functional version: on pickup, store origin; arrows move a "ghost position" — simplest: actually move card in model on each arrow and re-render, keeping focus on the card. On Escape restore original. On Space finalize and sweep. Announce via live region.

Actually simplest robust: on pickup, keep card in place visually with class `picked` (lime outline). Arrow keys move it in the model immediately (left/right column, up/down index), re-render and refocus. Escape → move back to origin, re-render, announce "Move cancelled." Space → sweep + announce.

Also priority keyboard shortcut? Skip - not required.

Delete button on card: small ✕ button, stopPropagation.

Counts: update header card count, column counts, footer counts, live result count.

Search: filter by title includes query (case-insensitive). Highlight matching portion with lime retracting underline animation: wrap match in `<mark class="hit">`. The underline animation: `mark` has background transparent, with ::after underline animating scaleX from 0... it should start at 0 and animate to 1 on matching result, then retract after700ms. So: add class, animation grows to full then holds, then retracts. I'll do a single keyframe animation `hitline700ms` where0% scaleX(0), 55% scaleX(1) (hold), 100% scaleX(0)? With opacity fading of mark color. Let's define:

```css
@keyframes hit { 0%{transform:scaleX(0)} 45%{transform:scaleX(1)} 80%{transform:scaleX(1)} 100%{transform:scaleX(0)} }
```
Applied to the ::after of mark. Re-triggered on each keystroke by re-render (new nodes) — fine.

Empty states: if search yields nothing in a column → `No matching cards.` If column empty and no query → `Nothing queued here.`

Modal: dialog-like div with role="dialog" aria-modal, focus trap, Escape, backdrop click, validation message with warning icon.

Theme: `document.documentElement.dataset.theme`.

Footer: dark band with counts, e.g. `QUEUE STATUS` + `TO DO 03 / IN PROGRESS 02 / DONE 01` + `IN-MEMORY SESSION · NO PERSISTENCE`.

Fonts: Google Fonts allowed? "no external files, images, fonts" — the task says no library/framework; the system prompt says everything inline, no network requests except CDN URLs explicitly allowed by the task. The task doesn't explicitly allow font CDNs. Safer: use system font stacks with condensed fallback: `'Arial Narrow', 'Helvetica Neue Condensed', sans-serif` for display... Hmm, aesthetics matter. Requirement says no external fonts ("Everything must be inline... no external files... except the CDN URLs explicitly allowed by the task"). The task allows no CDN. So I must use system fonts. I'll use font stacks: display/card: `"Arial Narrow", "Helvetica Neue", Impact...` Hmm Impact is too heavy.

Better: use `ui-sans-serif` with letter-spacing tweaks. For a condensed look: `font-stretch: condensed` works with variable fonts in system UI? Chrome supports font-stretch on system fonts somewhat. I'll use stack: `"Helvetica Neue", Arial, system-ui, sans-serif` with `font-stretch: 75%` and heavy weight for display — harmless if unsupported.

Mono: `ui-monospace, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace`.

I'll go with system stacks. Display: `"Arial Narrow", "Roboto Condensed", "Helvetica Neue", Arial, sans-serif` with font-weight 700 and letter-spacing -0.02em. Arial Narrow exists on Windows/macOS often.

Actually, hmm — visual quality judged by screenshot. Arial Narrow bold at 128px is decent condensed editorial. Good fallback chain.

Let me now write full HTML.

Date: compute live from Date (e.g., "TUE 14 MAY"). Use toLocaleDateString.

Let me write CSS.

```css
:root{
 --canvas:#F4F1EA; --surface:#FFFDF8; --dark:#111315; --ink:#111315; --ink-inv:#FFFDF8;
 --muted:#68706F; --rule:#C9C5BC; --rule-strong:#111315; --voltage:#C6FF3D; --coral:#FF6B4A; --blue:#4777FF; --low:#68706F;
 --overlay:rgba(17,19,21,.52);
 --grid-line: rgba(17,19,21,.06);
}
[data-theme=dark]{ --canvas:#111315; --surface:#191C1D; --ink:#FFFDF8; --ink-inv:#FFFDF8; --muted:#9AA1A0; --rule:#33383A; --rule-strong:#4A4F51; --overlay:rgba(0,0,0,.66); --grid-line: rgba(255,253,248,.06);}
```

Note header is dark in both themes; text inside uses fixed values. I'll use `--header-bg:#111315` fixed and `--header-fg:#FFFDF8`.

Body background canvas + grid paper pattern.

Layout widths: `.wrap{max-width:1440px;margin:0 auto;padding:0 32px}`.

Header: `position:sticky;top:0;z-index:40`.

Command deck: grid 12 col on desktop? Use simpler: `display:grid; grid-template-columns: minmax(0,1fr) auto; gap:24px; align-items:end`.

Title spans into utility zone: I'll let the title have `width: calc(100% + 160px)` for a grid break... careful with overflow at narrow widths. Use `margin-right:-120px` at ≥1100px only. Since parent is grid with search on the right, negative margin would overlap search. Risky. Alternative grid break: title extends beyond the wrap padding to the left edge (`margin-left:-8px`) and the utility strip is offset. Let me instead make the deck:

Row1: eyebrow + title (huge, can bleed slightly left via negative margin), and the utility strip on the right side aligned bottom. Row 2: supporting line left, search+toggle right.

Hmm. Simpler & safe:

```
.deck{display:grid;grid-template-columns:minmax(0,1.2fr) minmax(280px,.8fr);gap:32px;align-items:end;padding:48px 0 32px}
.deck-left{...} .deck-right{...}
```
Title with `margin-left:-6px` optical alignment and `letter-spacing:-.03em`, size clamp(56px,8vw,128px) — at 1440 viewport, 8vw = 115px. Left column width ~ (1440-64)*0.6 = 827px. "WORK QUEUE" at 115px Arial Narrow bold ≈ 115*0.5*10 chars ≈ 575px. Fits, could allow overflow into right zone visually by not wrapping — but the right column starts after gap. It's fine; the "break" I'll implement as the title extending left past the gutter by 6px and the utility strip having a top rule extending full width. Good enough and safe.

Actually a nicer grid break: put the title row full-width above, with search right-aligned in the same row. Let's do:

```
.deck{padding:44px 0 28px}
.deck-top{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:40px;align-items:end}
```
left: eyebrow, title, supporting line. right: search + theme toggle, aligned bottom, min-width 320px. Then utility strip full width below with top rule: 06 CARDS | 03 COLUMNS | LIVE QUEUE.

Title in left column: allow overflow to the right? It'll just wrap if too long. Set `white-space:nowrap` on title? At 375px width, clamp gives 56px min...56px "WORK QUEUE" nowrap in Arial Narrow ≈ 56*0.48*10=269px fits in 343px. OK but risky. I'll allow wrap (no nowrap) — at small screens it becomes two lines which is fine.

Hmm but if title overflows into search column on desktop... it wraps instead. Fine.

Board: `.board{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:24px}`.

Column: `position:relative; display:flex; flex-direction:column; min-height:560px; background: transparent;` Header with border-bottom 2px.

Wait — do columns have a surface background? Cards are surface on canvas; columns likely transparent with structural rules. Canvas background with grid lines. Good.

Column header padding: 0 0 12px, with count big at 32px on the right? Layout: label left (caption mono), count large below or inline. Design: 
```
[To do] ........  [03]  [+]
```
Let's do: top row: label + `+` button on right. Then count `03` at 32px beneath with border-bottom. Actually spec order: label, count, add button. I'll do:

```
<div class="col-head">
  <div class="col-title">To do <span class="add-inline">+</span></div>
  <div class="col-count">03</div>
</div>
```
Hmm. Let's do a flex row: left column with label (mono caption) and count (32px) stacked; right: add button. Then border-bottom 2px.

Card markup:

```html
<article class="card" draggable="true" tabindex="0" data-id="TK-104" role="listitem" aria-label="...">
  <span class="edge" style="background:var(--coral)"></span>
  <div class="card-top"><span class="prio prio-high"><i></i>HIGH / PRIORITY</span></div>
  <h3 class="card-title">Audit onboarding flow...</h3>
  <div class="card-foot">
    <span class="meta">#TK-104 · 9m ago</span>
    <span class="grip" aria-hidden="true">svg</span>
    <button class="del" aria-label="Delete card ...">svg</button>
  </div>
</article>
```

Card title with mark highlighting — put mark inside title.

Priority label colored with the priority color text? Badge: colored badge — background tinted + colored square. Use: text color = priority color (dark theme: lighten). For low `#68706F` in dark theme it's ok-ish on #191C1D... contrast low. Use `--low-ink` var: light `#68706F`, dark `#9AA1A0`. Blue `#4777FF` on dark fine. Coral fine.

Badge style: `display:inline-flex;gap:6px;align-items:center;border:1px solid currentColor;padding:3px 6px;font:600 10px/1 mono;letter-spacing:.08em;` with square dot. Plus background color-mix? Use rgba tints via CSS var per priority: `--p-bg`. Simpler: badge background = `color-mix(in srgb, currentColor 12%, transparent)` — color-mix supported in Chrome 111+. Fine, but I'll define explicit tints to be safe:

```css
.badge.high{color:var(--coral); background:rgba(255,107,74,.12); border-color:rgba(255,107,74,.5)}
```
Etc. On dark theme these are fine.

Left edge: 5px bar via `border-left:5px solid var(--edge)` on card, or absolute span. Use border-left on card for simplicity — but then radius 0 anyway. Card border: 1px solid var(--ink) — with border-left 5px solid edge color. That gives strong black border + colored edge. 

Card offsets: `:nth-child(2) margin-left:12px; :nth-child(4) margin-left:8px;` with `margin-right:0`? "visually offset horizontally by 0/12/0/8". I'll do `transform: translateX(...)` — but transform conflicts with hover translateY. Use margin-left with matching negative? Just margin-left, and reduce width accordingly (card width shrinks) — that's fine, "slips on pinboard". Apply to cards in stack: nth-child(2){margin-left:12px} nth-child(4){margin-left:8px}. Also matching margin-right so they don't overflow? Not needed.

Hmm but when dragging/reordering the nth-child changes causing jump. Acceptable.

Actually careful: margin-left on cards inside flex column — fine.

Drop rail element: `<div class="rail" id="rail"><span>DROP HERE</span></div>` height 2px, but with label needs some height. Make rail height 0 with a ::after line and a label absolutely positioned above. Let's: `.rail{height:2px;background:var(--voltage);position:relative;transform:scaleX(0);transform-origin:left;animation:railin 180ms forwards}` with `.rail::after{content:'DROP HERE';position:absolute;right:0;top:-16px;font:600 10px mono;color:var(--voltage);background:var(--ink);padding:2px 4px}`. In dark theme background ink is light — text lime on light bg ok.

Column drag-over: `.stack.over{outline:2px solid var(--voltage); outline-offset:6px}` plus label `DROP CARDS HERE` — I'll add to column: `.column.drag-over .col-head::after{content:'DROP CARDS HERE'}` styled lime. Simpler: `.column.drag-over .drop-hint{display:block}` with a span in head.

Sweep: element inside column: `<div class="sweep"></div>` absolutely at top of stack.

Now JS.

Data model:
```js
const state = { columns:[{id:'todo',name:'To do'},{id:'progress',...},{id:'done',...}], cards:[{id,title,priority,column,order,created}] , query:'', theme:'light', drag:null, picked:null }
```
Card order: keep arrays per column? Easier: `state.cards` array of objects; each has `col` and `idx`. Rendering: filter by col, sort by idx. Moving: reassign col and idx values by splicing.

Simplify: maintain `state.columns[i].cards = [cardObj,...]`. That makes everything easy. Let's do that: `columns: [{id:'todo', name:'To do', cards:[...]}, ...]`.

Render:
```js
function render(){ for each col: build stack html from visible cards }
```
Visible = query ? title.toLowerCase().includes(q) : all.

Highlight: escape HTML then wrap match.

Add card: to 'todo' column by default, prepend (top) — new cards appear at top of To do with entry animation.

Delete: remove from its column.

Counts: total = all cards; per column = all cards in col (not filtered) — but counts should reflect search? Count label shows column card count. I'll show visible count when searching? Spec: "each showing its card count". Keep total count of cards in column; but live result count shows matching. Hmm, during search, showing unfiltered counts while list is filtered is slightly inconsistent. I'll show count = visible cards when query active? I'll keep column count = actual cards (always), and the live count shows "X OF Y CARDS MATCH". That's clearer. Actually if column shows 03 but only 1 card visible, looks buggy. Better: column count shows visible count when searching, and add `/03` total? Overkill. Decision: column count always = number of cards in column (unfiltered), and when a query is active, show the filtered count in a muted smaller form? Simplest clean: count shows visible cards when query non-empty, else total. Plus live count line explains. I'll do: count = visible when query else total, and title attribute. Fine.

Hmm, but after filtering, empty columns show 00 with "No matching cards." — that reads correctly. Good, go with visible count.

Footer counts: total per column (unfiltered)? Use same rule for consistency with header (total cards). Header count = total cards `06 CARDS`. Footer: TO DO 03 / IN PROGRESS 02 / DONE 01 (totals). Live region shows matching.

Drag handlers: attach to stack container with event delegation.

```js
stack.addEventListener('dragover', e=>{ if(!dragId) return; e.preventDefault(); ... })
```
Compute insert index: iterate cards (non-dragging, non-hidden) getBoundingClientRect midpoints.

Let me implement helper:
```js
function indexFromPoint(stack, y){
  const items=[...stack.querySelectorAll('.card:not(.dragging)')];
  for(let i=0;i<items.length;i++){ const r=items[i].getBoundingClientRect(); if(y < r.top + r.height/2) return i; }
  return items.length;
}
```
Then place rail before items[index] or append.

On dragleave of stack (relatedTarget outside), remove rail.

On drop: determine target column id from stack dataset and index; move card in model; re-render; then sweep + tint the card.

Since render replaces DOM, capture card id first.

Queue sweep function:
```js
function queueSweep(colId, cardId){
  const col=...; const s=document.createElement('div'); s.className='sweep'; col.appendChild(s); s.addEventListener('animationend',()=>s.remove());
  const el=document.querySelector(`[data-id="${cardId}"]`); if(el){el.classList.add('touched'); setTimeout(()=>el.classList.remove('touched'),460)}
}
```
Reduced motion: skip animation, just set class for a beat.

Keyboard drag on card:
```js
card keydown:
 if(e.key===' '||e.key==='Spacebar'){ e.preventDefault(); if(!picked){picked={id,colIdx,cardIdx,orig...}; card.classList.add('picked'); announce('Picked up ... Use arrow keys')} else {commit: picked=null; remove class; sweep;} }
 if(picked && ArrowLeft/Right/Up/Down){move in model; render(); refocus card; keep picked state }
 Escape: restore original position; render; announce cancelled.
```
Since render rebuilds DOM, need to re-apply `.picked` — track `pickedId` globally, and in render add class if card.id===pickedId. And refocus after render if document.activeElement was a card / keyboardMode flag.

Restore original: store `{colIndex, cardIndex}` before moving.

Implementation of move for keyboard:
- Left: if colIdx>0 → move to end (index 0) of previous column.
- Right: similar to next column.
- Up/Down: swap within column.

After move: picked.colIdx/cardIdx updated. Announce `Moved to In progress, position 2`.

Focus: after render, `document.querySelector('[data-id=...]').focus()` if keyboard mode.

Careful: card focus after render — keep flag `focusAfterRender`.

Also click on card should focus it (for keyboard pickup). Cards tabindex=0 so click focuses naturally? Clicking a div with tabindex focuses it in Chrome. Yes.

Add card button in column head: `+` with aria-label "Add card to To do", sets default column in modal.

Modal: build in HTML, hidden. Open: show backdrop, add `.open`, focus title input, trap tab. Close: reverse, return focus.

Validation: on submit, trim; if empty → show error, aria-invalid, focus input; else create card, close, render, entry animation, announce.

Focus trap: keydown on modal, if Tab, cycle among focusable.

Search: input event → state.query, render, update live region with match count.

Theme toggle: two buttons, both aria-pressed and label text change.

Mobile responsive: <760px board becomes horizontal scroll: `display:flex; overflow-x:auto; scroll-snap-type:x mandatory` with columns `flex:0 0 calc(100% - 24px)` and scroll-snap-align:start. Header two rows. Deck stacks. Add button fixed circular bottom-right? There's already a button in header; the spec suggests fixed at bottom-right on mobile. I'll keep header button but make it compact; adding a fixed FAB too might duplicate. I'll make the header add button compact and add nothing else — actually design notes say fixed circular icon button at bottom-right on mobile. I'll implement: on mobile, hide the header text button, show a fixed FAB. Good touch.

Board container needs `min-width:0` and no horizontal page scroll: `overflow-x:auto` on board only.

Now watch: at 375px, the header two rows, deck, board horizontal scroll showing column 1 with 24px of next. Good.

Let me write the code.

HTML skeleton:

```html
<!DOCTYPE html><html lang="en" data-theme="light"><head>...
<body>
<a class="skip" href="#board">Skip to board</a>
<header>...</header>
<main>
 <section class="deck">...</section>
 <section class="board" id="board" aria-label="Kanban board">... columns ...</section>
</main>
<footer>...</footer>
<div class="modal-backdrop" id="backdrop" hidden>...</div>
<div class="live" aria-live="polite" role="status"></div>
```

Columns rendered by JS (innerHTML).

Icons inline SVG — I'll define a small `icon()` helper in JS for delete/grip/plus, and static SVGs in HTML for header buttons.

Let me write it.

CSS details for header:

```css
.topbar{position:sticky;top:0;z-index:60;background:var(--dark);color:#FFFDF8;border-bottom:2px solid var(--voltage)}
```
Hmm, dark header band with bottom rule — a lime bottom rule might be too much lime. Design says lime sparingly. I'll use `border-bottom:1px solid rgba(255,253,248,.16)` and a thin lime accent under the wordmark? Keep: header bottom border `1px solid var(--ink)` in light theme? On dark band, use `rgba(255,253,248,.18)`. Fine.

Sticky header height 72px.

Buttons:
```css
.btn{font:600 12px/1 var(--mono);letter-spacing:.06em;text-transform:uppercase;padding:12px 16px;border:1px solid var(--ink);background:transparent;color:var(--ink);cursor:pointer;transition:...}
.btn-primary{background:var(--voltage);border-color:var(--ink);color:#111315}
.btn-primary:hover{background:#111315;color:#C6FF3D;border-color:#111315}
```
Header primary: on dark bg, border-color #111315 invisible; use border lime? Let's `.btn-primary{border:1px solid var(--ink)}` and in header the ink var — in light theme ink is #111315 which on dark header is invisible border but bg lime so edge is fine. Hover: bg #111315 with lime text on dark header → invisible. Fix: define hover as `background:#111315;color:var(--voltage); box-shadow: inset 0 0 0 1px var(--voltage)` — hmm inset shadow is a shadow (not allowed? "No box shadows" refers to card elevation). Use `border-color:var(--voltage)` on hover. Simple: hover → `background:var(--dark); color:var(--voltage); border-color:var(--voltage)`. Works on both light page and dark header. 

Buttons with `:focus-visible{outline:3px solid var(--voltage);outline-offset:2px}`.

Theme toggle: small rect button with sun/moon icon + label "LIGHT"/"DARK".

Search field with svg icon inside, width minmax(240px,420px).

Utility strip: `display:flex;gap:32px;border-top:1px solid var(--rule-strong);border-bottom:1px solid var(--rule-strong);padding:12px 0;font:600 11px mono` with separators. Add `LIVE QUEUE` with lime dot.

Column head sticky? no.

Let's write everything now, carefully, complete.

Card title: `font-family:var(--display);font-size:24px;font-weight:700;line-height:1.05` — in the fallback stack. Good.

Now, timestamps: relative like `9m ago`, `2h ago`. Store as label strings.

Initial data with column and order.

Render functions:

```js
function columnHtml(col, index){
  const q=state.query.trim().toLowerCase();
  const visible = col.cards.filter(c=>!q || c.title.toLowerCase().includes(q));
  ...
}
```

Highlight function:
```js
function highlight(title,q){
  const esc = escapeHtml(title);
  if(!q) return esc;
  const lower=title.toLowerCase(); const i=lower.indexOf(q); if(i<0) return esc;
  return escapeHtml(title.slice(0,i))+'<mark class="hit">'+escapeHtml(title.slice(i,i+q.length))+'</mark>'+escapeHtml(title.slice(i+q.length));
}
```
Only first match — fine.

Now write DOM for cards in JS template string.

Card aria-label: `${title}, priority ${priority}, in ${col.name}, position ${n} of ${len}. Press Space to pick up.`

Delete button inside card: `draggable` on parent may interfere with button clicks; set `draggable="true"` on card and on button `draggable="false"`. Delete works on click.

Drag start: don't start when target is a button — use `dragstart` on card and check `e.target.closest('button')`. Since button is draggable=false, browser handles it. Fine.

HTML5 drag image default is fine.

Add `card.classList.add('dragging')` on dragstart (after timeout) and remove on dragend.

Also when dragging, the card's slot: keep the original in place with opacity .4? Design: destination focus. I'll set `.dragging{opacity:.35; border-style:dashed}`? Let's do `.dragging{opacity:.35}` and rail shows insertion.

drop handling:

```js
stack.addEventListener('drop', e=>{ e.preventDefault(); const id = dragId||e.dataTransfer.getData('text/plain'); ... })
```

Move function:
```js
function findCard(id){ for(const col of state.columns){const i=col.cards.findIndex(c=>c.id===id); if(i>=0) return {col,i,card:col.cards[i]};} }
function moveCard(id, targetColId, index){
  const src=findCard(id); if(!src) return null;
  const [card]=src.col.cards.splice(src.i,1);
  const target=state.columns.find(c=>c.id===targetColId);
  let idx=Math.max(0,Math.min(index,target.cards.length));
  target.cards.splice(idx,0,card);
  return card;
}
```

After drop: render, sweep.

Sweep should run on: successful drop (drag), keyboard commit, and clicking a card (single click triggers Queue Sweep). Clicking a card triggering sweep: I'll add click listener on card (not on buttons) → sweep that column + tint card. That satisfies "single click on a card" requirement and gives interactive feedback. But clicking also just focuses — fine, sweep is a nice touch.

Careful: click fires after drag ends sometimes. Use a `justDragged` flag to skip.

Entry animation for new card: after render, `el.classList.add('enter')` and remove after animation.

Now the modal HTML:

```html
<div class="scrim" id="scrim" hidden>
  <div class="modal" role="dialog" aria-modal="true" aria-labelledby="modal-title">
    <div class="modal-head"><h2 id="modal-title">ADD TO THE QUEUE</h2><button class="icon-btn" id="closeModal" aria-label="Close dialog">×svg</button></div>
    <form id="cardForm" novalidate>
      <label for="titleInput">CARD TITLE <span>*</span></label>
      <input id="titleInput" ... aria-describedby="titleErr">
      <p class="err" id="titleErr" hidden>icon Give this card a title before adding it.</p>
      <fieldset?  <span>PRIORITY</span>
      <div class="seg" role="radiogroup" aria-label="Priority">3 buttons role=radio aria-checked</div>
      <div class="modal-actions"><button type=button class="btn" id="cancelBtn">CANCEL</button><button class="btn btn-primary" type="submit">ADD CARD</button></div>
    </form>
  </div>
</div>
```

Segmented control: use buttons with role="radio" in role="radiogroup", keyboard arrows optional; click selects. Also make them real inputs? Use `<label><input type=radio class=sr-only>` for simplicity + native keyboard. But styling focus on the label: `input:focus-visible + span`? Let's do:

```html
<label class="seg-item"><input type="radio" name="priority" value="low"><span>LOW</span></label>
```
CSS: input visually hidden (but focusable), `.seg-item:has(input:checked)` — :has supported in Chrome 105+. Safe. Focus ring: `.seg-item:has(input:focus-visible){outline:3px solid lime;outline-offset:2px}`. Good. Also `.seg-item:has(input:checked)` gets colored background.

Color per item: low/medium/high → set border/bg via nth or class.

Priority selected default: medium? Spec order: name, priority, status. Default `medium`... I'll default to "medium"? Actually cards default better as "low"? I'll default medium (middle) — hmm, new card in To do, default low? I'll default `medium`. Eh — choose `low` to not over-signal urgency? Design doesn't say. Default `medium` is typical. Go with medium.

Scrim open: `.scrim{display:none}` `[hidden]` must be overridden when open — I'll use class `.open` and not hidden attr for animation: set `hidden` off, then rAF add `.open`. Closing: remove `.open`, after 160ms set `hidden`.

Focus trap: keydown handler on scrim.

Now footer:

```html
<footer class="statusbar">
 <div class="wrap status-inner">
   <span class="mono">QUEUE STATUS</span>
   <span class="mono" id="footCounts">TO DO 03 / IN PROGRESS 02 / DONE 01</span>
   <span class="mono dim">IN-MEMORY SESSION · NO PERSISTENCE</span>
 </div>
</footer>
```

Live region also displays `06 OF 06 CARDS MATCH` in the deck (visible element with aria-live).

Let me place: in deck-right under search: `<div class="resultcount" aria-live="polite">06 OF 06 CARDS MATCH</div>`.

Grid paper: on body `background-image: linear-gradient(...)` with 32px grid using rgba lines.

Now writing CSS fully. Watch: no horizontal scroll. `html,body{overflow-x:hidden}`? Better avoid hidden (breaks sticky? no). I'll ensure wrap padding and no negative margins beyond padding... I have title `margin-left:-6px` only. Fine.

Let me write out the file.

Also `.board` needs relative positioning? Sweep is inside column (position:relative). ok.

Column vertical rules: `.column + .column::before{content:'';position:absolute;left:-13px;top:0;bottom:0;width:2px;background:var(--rule-strong)}` — need gap 24 → center at -12, width 2 → left:-13. Good. On mobile (horizontal flex) disable: `left:-13px` between columns with gap 24 — still fine actually, but with snap scroll it's ok. I'll disable on mobile to keep cards clean: set `display:none` under 760.

Column min-height 560 desktop; mobile maybe 420.

Stack min-height to show empty rail: `.stack{min-height:320px}`.

Empty rail:
```html
<div class="empty"><div class="rail-empty"></div><p>Nothing queued here.</p><span class="mono">Drag a card here or use Add card.</span></div>
```
Rail-empty: `height:0;border-top:2px dashed var(--rule)` with padding — for search empty: `No matching cards.`

OK, writing now.

JS announce function sets `#srLive` textContent.

Also keyboard pickup hint: show a small badge on picked card `MOVING — ARROWS TO PLACE · SPACE TO DROP`.

Card picked class: `outline:2px solid var(--voltage); outline-offset:4px`.

Also keyboard pickup hint: show a small badge on picked card `MOVING — ARROWS TO PLACE · SPACE TO DROP`.

Let's write the code. I'll be thorough but efficient.

One more: header date format `TUE 14 MAY` — compute:
```js
const d=new Date();
const s=d.toLocaleDateString('en-GB',{weekday:'short',day:'2-digit',month:'short'}).toUpperCase().replace(',','');
```
Gives "TUE 14 MAY". Good.

Count in header: pad2(total)+' CARDS'.

Add card modal default column: variable `state.addTarget='todo'` set by + buttons.

Entry animation must run for card created via modal: after render, `data-new` card gets `.enter`.

Implementation: keep `state.flash={id, type:'enter'|'sweep'}` consumed on next render.

Let's code:

```js
function render(){
  board.innerHTML = state.columns.map(columnHtml).join('');
  updateCounts();
  if(state.flash){ const el=board.querySelector(`[data-id="${state.flash.id}"]`); if(el){ if(state.flash.type==='enter') el.classList.add('enter'); else queueSweep(state.flash.colId, el);} state.flash=null; }
  if(focusId){ const el=board.querySelector(...); el&&el.focus(); focusId=null; }
}
```
But querySelector with id containing chars — ids like `TK-104` fine; use `CSS.escape` or attribute selector with quotes — fine.

queueSweep(colId, cardEl): create sweep div appended to that column element.

Also need event listeners after re-render — use delegation on `#board` for click, keydown, dragstart, etc. Delegation avoids rebinding. 

Board-level delegation:

```js
board.addEventListener('dragstart', e=>{ const card=e.target.closest('.card'); if(!card) return; ...});
board.addEventListener('dragend', ...)
board.addEventListener('dragover', e=>{ const stack=e.target.closest('.stack'); if(!stack||!dragId) return; e.preventDefault(); ...});
board.addEventListener('drop', ...)
board.addEventListener('dragleave', e=>{ if(e.target.closest('.stack') && !stack.contains(e.relatedTarget)) removeRail() })
board.addEventListener('click', ...)
board.addEventListener('keydown', ...)
```

dragover: also add class `over` to column.

Careful with dragleave relatedTarget null. I'll instead remove rail on `dragleave` of stack when `!stack.contains(e.relatedTarget)`.

Rail insertion within dragover: if rail already in right position, skip. Compute index among `.card:not(.dragging)`; but rail itself isn't a card so ok.

Let me write indexFromPoint including rail removal first? Rail is not `.card` so doesn't affect loop. Then insert:
```js
const items=[...stack.querySelectorAll('.card:not(.dragging)')];
let ref=null;
for(const it of items){ const r=it.getBoundingClientRect(); if(e.clientY < r.top+r.height/2){ ref=it; break; } }
stack.insertBefore(rail, ref);
```
If ref is the dragging card... excluded from items; but if dragging card is in same stack and ref would be it — excluded, so rail may be placed after it. Acceptable (dragging card shows dimmed in place). Better: hide the dragging card entirely (display:none) so layout reflects removal. But then re-entry animation... Simpler: `.dragging{display:none}`? Then the drop indicator index aligns exactly with final position. I like that — but "drag ghost" is the browser's. However if display:none, the rail insertion index computation excludes it correctly and card visually vanishes from origin — spec says destination focus, source removal is fine. But if the user cancels (dragend outside), card reappears — fine.

Hmm, but with display:none, the stack height collapses and layout jumps. That's normal for drag-and-drop. I'll use `opacity:.35` instead but exclude from index calc — the issue: card still occupies space, so rail placed relative to remaining items may be off by its slot. Let's just use display:none via class `.dragging{display:none}`. Wait — spec: "The dragged card is lifted with a slight scale and opacity change." So keep it visible but dimmed: `.dragging{opacity:.4; transform:scale(1.02)}`. Then exclude from index and accept slight offset... Actually I can include the dragging card in the index computation but skip inserting rail at its position: simpler approach — compute index over all cards (including dragging), then set rail before items[index] where items excludes dragging. That's essentially the same.

Alternative cleaner: compute index over all cards including dragging; if index > draggingIndex, target position in the filtered list = index-1. Then place rail before the item at that filtered index (or append). That's exact. Let's do:

```js
const all=[...stack.querySelectorAll('.card')];
let idx=all.length;
for(let i=0;i<all.length;i++){ if(e.clientY < all[i].getBoundingClientRect().top + h/2){ idx=i; break; } }
const draggingPos = all.findIndex(c=>c.classList.contains('dragging'));
let filtered = all.filter(c=>!c.classList.contains('dragging'));
let fidx = idx; if(draggingPos>=0 && idx>draggingPos) fidx = idx-1;
const ref = filtered[fidx] || null;
stack.insertBefore(rail, ref);
```
Good.

Drop: recompute same way → target index in model = fidx (relative to column.cards minus dragged card if same column). Since moveCard splices out first then inserts at index computed on the list *excluding* the dragged card — matches fidx. 

Store `lastDrop = {colId, index}` on dragover, use on drop.

Also store `dragId` variable.

Now the "DROP CARDS HERE" label in column head when over.

Write the code now.

Also `announce()` live region — I have visible result count with aria-live plus a separate sr-only live region for moves. Two live regions ok.

Let me write final HTML.

Sizes: mono font var:
```css
--mono: ui-monospace, "SFMono-Regular", Menlo, Consolas, "Liberation Mono", monospace;
--display: "Arial Narrow","Roboto Condensed","Helvetica Neue",Arial,sans-serif;
--sans: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
```

For display I'll add `font-stretch:condensed`.

Now write.

Mobile FAB: `.fab{display:none}` and `@media(max-width:759px){.fab{display:flex;position:fixed;right:16px;bottom:16px;z-index:50;width:56px;height:56px;border-radius:50%;background:var(--voltage);border:1px solid var(--ink);...}}` and hide `.btn-add-header`. Note: border-radius exception allowed for FAB (icon button). Also footer at bottom — FAB overlaps footer; fine, add `bottom:16px` and footer has padding. Ok.

Also make sure page doesn't have horizontal scrollbar on mobile: board scroll container handles columns.

Card title wrapping: `-webkit-line-clamp`? No, allow 2-3 lines.

Let me now also handle: `.stack` cards alternate offset nth-child — but with rail element inserted, nth-child counts change (rail is a child). Use `.card:nth-of-type()`? Cards are `article`, rail is `div` — nth-of-type on article works if all cards are articles and rail is div: `.stack > article:nth-of-type(2)` counts articles only. Good — use `article:nth-of-type(2n)` etc. Actually nth-of-type counts among siblings of same type (article). So `.stack > article:nth-of-type(2){margin-left:12px}` and `:nth-of-type(4){margin-left:8px}`.

But cards hidden by search — fine.

Hmm: also `enter` animation uses transform translateY; combined with margin offsets fine.

Write the sweep CSS:
```css
.sweep{position:absolute;left:0;right:0;top:0;height:1px;background:var(--voltage);transform-origin:left;animation:sweep 420ms cubic-bezier(.22,1,.36,1) forwards;pointer-events:none;z-index:5}
@keyframes sweep{0%{transform:scaleX(0);opacity:1}70%{transform:scaleX(1);opacity:1}100%{transform:scaleX(1);opacity:0}}
```
Place at top of the column (over header). Append to `.column` with position relative. Column top = header top; a lime line across header top edge. Good.

Card touched: `.card.touched{border-left-color:var(--voltage);background:linear-gradient(...)}` — no gradients allowed. Use `background:rgba(198,255,61,.18)` transition420ms. Define `.card{transition:background 200ms, border-color 200ms}` and `.touched{background:#EFFFD0}` for light; use `color-mix`? Just define var `--tint: #EAF9C4` light / `#243017` dark. Set `.card.touched{background:var(--tint);border-left-color:var(--voltage);}`.

Also `.touched` should transition back — remove class after 700ms.

OK. Writing the final answer now.

Structure of JS in order:
1. consts/icons
2. state/data
3. helpers (pad, escapeHtml, find)
4. render
5. counts/update
6. search
7. theme
8. modal
9. drag
10. keyboard
11. click delegation
12. init

Let me write.

For card HTML generation:

```js
function cardHtml(card, colName, pos, total, q){
  const p=PRIORITY[card.priority];
  return `<article class="card" data-id="${card.id}" draggable="true" tabindex="0" role="listitem" aria-label="..." style="--edge:${p.color}">
    <div class="card-body">
      <span class="badge ${card.priority}"><i></i>${p.label}</span>
      <h3 class="card-title">${highlight(card.title,q)}</h3>
    </div>
    <div class="card-foot">
      <span class="mono meta">#${card.id} · ${card.time}</span>
      <span class="grip" title="Drag to move">${gripSvg}</span>
      <button class="del" data-del="${card.id}" draggable="false" aria-label="Delete card ${escapeAttr(card.title)}">${trashSvg}</button>
    </div>
  </article>`;
}
```
Card has border-left 5px solid var(--edge).

badge label: `HIGH / PRIORITY`, `MEDIUM / PRIORITY`, `LOW / PRIORITY`.

PRIORITY map:
```js
low:{label:'LOW / PRIORITY', color:'var(--low)'}
```
edge color uses same.

Wait: left edge for medium = blue, high = coral, low = muted. Good.

But high priority should also maybe have a stronger top? Not needed.

Card height min 148px: `min-height:148px; display:flex; flex-direction:column; justify-content:space-between; gap:16px`.

Now aria-label: `${card.title}. Priority ${card.priority}. In ${colName}, position ${pos} of ${total}. Press Space to pick up, arrow keys to move, Escape to cancel.`

Alright, writing it all out.

Column html:

```js
function columnHtml(col, ci){
  const q=state.query.trim().toLowerCase();
  const visible=col.cards.filter(c=>!q||c.title.toLowerCase().includes(q));
  const cards = visible.length ? visible.map((c,i)=>cardHtml(c,col.name,i+1,visible.length,q)).join('') : emptyHtml(q);
  return `<section class="column" data-col="${col.id}" aria-labelledby="h-${col.id}">
    <div class="col-head">
      <div>
        <p class="mono caption" id="h-${col.id}">${col.name.toUpperCase()}</p>
        <p class="count">${pad(visible.length)}</p>
      </div>
      <button class="add-inline" data-add="${col.id}" aria-label="Add card to ${col.name}" title="Add card to ${col.name}">${plusSvg}</button>
      <span class="drop-hint mono">DROP CARDS HERE</span>
    </div>
    <div class="stack" role="list" aria-label="${col.name} cards">${cards}</div>
  </section>`;
}
```

drop-hint in head: absolutely positioned right, hidden unless `.drag-over`.

count at 32px mono 600 — put count next to label maybe. Layout: caption top, count below big. Head height then ~ 60px. Fine.

emptyHtml:
```js
`<div class="empty"><div class="rail-empty"></div><p>${q?'No matching cards.':'Nothing queued here.'}</p><span class="mono">${q?'Try a different search.':'Drag a card here or use Add card.'}</span></div>`
```

Now write final code. Also ensure `updateCounts` sets header count, footer, live count.

Live count text: `${pad(matchTotal)} OF ${pad(total)} CARDS MATCH` and when no query: `${pad(total)} CARDS · ALL VISIBLE`. Keep simple: if query → match text; else `06 OF 06 CARDS MATCH`.

Let me write the whole file.

CSS length is substantial; let's go.

Also add `prefers-reduced-motion` block.

Escape HTML function and attr escape.

Final check on modal focus trap and Escape global (Escape also closes modal, cancels keyboard pick).

Global keydown: if modal open → handled in modal; else if picked → handle arrows/space/escape.

Write it.

One detail: cards' `tabindex="0"` and role listitem — with keyboard instructions in aria-label. Fine.

Click on card triggers sweep — but click also happens when clicking delete button (stop propagation) and add button (not in card). Implement in delegation:

```js
board.addEventListener('click', e=>{
  const del=e.target.closest('[data-del]'); if(del){ removeCard(del.dataset.del); return; }
  const add=e.target.closest('[data-add]'); if(add){ openModal(add.dataset.add); return; }
  const card=e.target.closest('.card'); if(card && !justDragged){ sweepFor(card); }
});
```

sweepFor(card): find col id of card, create sweep, tint card.

Alright. Also make sure clicking a card doesn't trigger sweep when user is selecting text — minor.

For the sweep element, append to `.column[data-col=id]`.

Final: write code.

Footer mobile stacking.

Header mobile two rows: use flex-wrap with `.topbar-inner{display:flex;gap:16px;align-items:center;min-height:72px;flex-wrap:wrap;padding:10px 0}` and on mobile give left group and actions widths. I'll structure:

```html
<div class="wrap topbar-inner">
  <div class="brand"><span class="mark">KANBAN<span>//03</span></span></div>
  <div class="bar-meta"><span id="dateLabel">…</span><span class="sep"></span><span id="cardCount">06 CARDS</span></div>
  <div class="bar-actions"><button theme><svg>…</button><button class="btn btn-primary btn-add">+ ADD CARD</button></div>
</div>
```
Desktop: brand left, meta next to brand with gap, actions pushed right (margin-left:auto).
Mobile: `flex-wrap:wrap` with brand+meta on row 1 (meta margin-left auto), actions row 2 full width... Spec: two rows with controls right. I'll do: row1 = brand + meta(right), row2 = actions right-aligned. Use `.bar-actions{margin-left:auto}` and on mobile `width:100%;justify-content:flex-end` while brand/meta share row: put brand and meta in a `.bar-left` wrapper? With flex-wrap and order: brand(order1), meta(order2, margin-left:auto → but wraps). Simplest: 

```html
<div class="topbar-inner">
  <div class="bar-row-1"><brand><meta></div>  -- hmm
```
I'll structure: `.topbar-inner{display:flex;flex-wrap:wrap;align-items:center;gap:12px 20px}` with `.brand{}`, `.bar-meta{margin-right:auto}` (pushes actions right), `.bar-actions{margin-left:auto}`. On desktop: brand, meta, then actions right — with meta margin-right:auto and actions margin-left:auto both, meta ends up left-ish next to brand and actions right. Good. On mobile: allow wrap — set `.bar-meta{order:3;width:100%}` hmm. Mobile: brand (order 1), actions (order 2, margin-left:auto), meta (order 3, width 100%). That gives row1: brand + actions, row2: meta. But spec wants controls right on row 2 and wordmark left on row 1. So mobile: brand order1, meta order2 margin-right:auto? Let's: row1 = brand + meta (meta pushed right with margin-left:auto), row2 = actions (width:100%, justify-content:flex-end). That matches spec. Implement:

mobile: `.bar-meta{margin-left:auto;margin-right:0;order:2}` `.bar-actions{order:3;width:100%;justify-content:flex-end;margin-left:0}` and `.brand{order:1}`. Desktop: brand order1, meta order2 (margin-left:0, margin-right:auto), actions order3 margin-left:auto.

Careful with `margin-right:auto` on meta in desktop and `margin-left:auto` on actions: both push apart → meta sits right after brand? With meta margin-right:auto and actions margin-left:auto, extra space distributed between them → meta stays left next to brand, actions right. Good.

Mobile: meta margin-left:auto → meta right of brand; actions full width row. 

Use media query to adjust. Good.

Now write.

Height: sticky header ~72 desktop.

Let's finalize the code text.

I'll write CSS in a logical order. Let's go.

Also `body{min-height:100vh; display:flex; flex-direction:column}` with footer at bottom: `main{flex:1}`.

Careful: sticky header inside body flex — sticky works if body is the scroll container ancestor; body as flex container doesn't break position:sticky (needs overflow not on ancestors). Should be fine.

Actually sticky inside `body` flex: the sticky element's containing block is body; scrolling happens on html/viewport. Works.

Write now.

Code for `enter` animation:
```css
@keyframes cardIn{from{opacity:0;transform:translateY(-18px) scale(.98)}to{opacity:1;transform:none}}
.card.enter{animation:cardIn 360ms cubic-bezier(.16,1,.3,1)}
```
But `.card` has no transform normally; ok.

Hit animation:
```css
mark.hit{background:transparent;color:inherit;position:relative}
mark.hit::after{content:'';position:absolute;left:0;right:0;bottom:-2px;height:2px;background:var(--voltage);transform-origin:left;animation:hit 900ms cubic-bezier(.22,1,.36,1) forwards}
@keyframes hit{0%{transform:scaleX(0)}40%{transform:scaleX(1)}75%{transform:scaleX(1)}100%{transform:scaleX(0)}}
```
Also give the text a subtle lime highlight: `background:rgba(198,255,61,.35)` initially then? Keep underline only plus color. Fine, maybe `box-shadow: inset 0 -0.35em 0 rgba(198,255,61,.45)` — that's a box-shadow used as highlight, not elevation. Eh, allowed? "No box shadows on cards" — this is inside title. I'll skip and use `background:rgba(198,255,61,.35)` on mark which fades... but then underline animation plus static bg. It's fine and readable. Actually spec says underline is the confirmation. I'll do both: mark has faint lime bg (helps visibility) — but "Do not flood entire cards with lime" — small text highlight ok. I'll keep mark background transparent and just the animated underline, plus `color:var(--ink)` and `font-weight:700`. Underline lime on cream is visible. Good.

Let's write final answer.

Total count padding: pad2.

Data:
```js
const initial=[
 {id:'TK-104',title:'Audit onboarding flow',priority:'high',col:'todo',time:'9m ago'},
 {id:'TK-105',title:'Prepare Q3 campaign brief',priority:'medium',col:'todo',time:'42m ago'},
 {id:'TK-106',title:'Review mobile empty states',priority:'low',col:'todo',time:'1h ago'},
 {id:'TK-101',title:'Fix checkout validation error',priority:'high',col:'progress',time:'18m ago'},
 {id:'TK-102',title:'Write release notes for v2.4',priority:'medium',col:'progress',time:'2h ago'},
 {id:'TK-097',title:'Publish support handoff checklist',priority:'low',col:'done',time:'Yesterday'},
];
```
Count: 3/2/1 = 6. ✓

Next id counter for new cards: TK-107...

Write it all now.

For the drop hint label styling:
```css
.drop-hint{display:none;position:absolute;right:0;top:2px;color:var(--voltage);background:var(--ink);padding:3px 6px;font:600 10px/1 var(--mono);letter-spacing:.08em}
.column.drag-over .drop-hint{display:block}
.column.drag-over{outline:2px solid var(--voltage);outline-offset:8px}
```
Careful: outline on column may collide with neighbor — offset 8 into gap 24, ok.

Stack over class not needed then.

Now really write.

For keyboard picked card visible hint, add inside card when picked:
Render: if `state.pickedId===card.id` add class `picked` and a `<span class="pick-hint mono">…</span>`. Add as first child of card-body? Put after badge. I'll append `<span class="pick-hint mono">MOVE · ←→ columns · ↑↓ order · SPACE drop · ESC cancel</span>` — hidden unless picked (class on card shows it).

Now writing the answer.

Let me make sure the drag `drop` handler works: `dragover` must preventDefault on the stack. Also allow drop on column (not just stack) — target closest('.stack') covers since stack fills column below head.

Also, on drop, remove rail, clear classes.

`dragId` cleared on dragend.

Edge: dropping onto a column with no cards (empty state) — the empty div is inside stack; `e.target.closest('.stack')` works.

Ok final code.

Also announce on drop: `Card moved to In progress, position 2.`

Let me write `announce(msg)`.

updateCounts:
```js
function updateCounts(){
  const total = state.columns.reduce((n,c)=>n+c.cards.length,0);
  $('#headerCount').textContent = pad(total)+' CARDS';
  const q=state.query.trim().toLowerCase();
  let matches=0; state.columns.forEach(c=>c.cards.forEach(x=>{ if(!q||x.title.toLowerCase().includes(q)) matches++; }));
  $('#resultCount').textContent = q ? `${pad(matches)} OF ${pad(total)} CARDS MATCH` : `${pad(total)} CARDS IN QUEUE`;
  $('#footCounts').textContent = state.columns.map(c=>`${c.name.toUpperCase()} ${pad(c.cards.length)}`).join(' / ');
}
```
Column counts are rendered in columnHtml as visible count — hmm I decided visible count. Let's render visible count in the column head. But then when searching, column count 0 with "No matching cards" — consistent. OK.

Hmm, but total in header vs column counts mismatch during search is explained by the result line. Fine.

Wait: footer counts use totals — while searching could confuse. Use visible too? Footer says queue status — I'll use totals (real state). And header count totals. The result count explains search. But column head showing visible count while footer shows total for same column = inconsistent. Decision: column head always shows TOTAL cards in column; result count shows matches; empty column during search shows "No matching cards." with count showing total (e.g. 03) — that's the confusing case.

Better decision: column head shows visible count when searching AND footer/header show totals; the mismatch between column and footer only happens during active search which is clearly indicated by "X OF Y CARDS MATCH". I'll go with visible count in column heads. Done deliberating.

Hmm, actually simplest and least confusing: column head count = visible count always; when searching, footer counts = total (labeled queue status). Fine.

Go.

Writing CSS now in full, then JS.

I should double check `:has()` usage for radio styling — Chrome supports. Also fallback: add `input:checked + span` styling instead? Structure `<label class="seg"><input><span>LOW</span></label>` → `input:checked + span{...}` works everywhere. Use that for background/text, and `input:focus-visible + span{outline:...}`. But padding/box on span: make span the styled box (display:flex; padding). Then label is just wrapper. 

```css
.seg input{position:absolute;opacity:0;width:0;height:0}
.seg span{display:block;padding:9px 14px;border:1px solid var(--rule-strong);font:600 12px mono;cursor:pointer;margin-left:-1px}
.seg input:checked + span{background:var(--c);color:#111315;border-color:var(--ink);position:relative;z-index:1}
.seg input:focus-visible + span{outline:3px solid var(--voltage);outline-offset:2px;z-index:2}
```
with `--c` per label inline: low → `#E4E1D9`? Low priority badge color is muted gray — as a filled segment, use a light tint. Let's: low → `#DAD6CC` (light gray) text #111315; medium → `#4777FF` text #FFFDF8; high → `#FF6B4A` text #111315. In dark theme the low one is light gray, fine (it's a selected chip). Unchecked in dark: border var(--rule-strong) which in dark is #4A4F51, text var(--ink) light. Good.

Set inline style `--c:#...` on label.

Ok, write.

Also modal on dark: modal background `var(--surface)`.

Input: `background:var(--surface); border:2px solid var(--ink); color:var(--ink); padding:12px; font:400 16px var(--mono)` (16px avoids iOS zoom).

Error message coral with icon.

Now write out. I'll be careful with SVG icons (stroke currentColor).

Icons:
- search: `<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.75"><circle cx="11" cy="11" r="6.5"/><path d="M16 16l4.5 4.5"/></svg>`
- plus: `<path d="M12 5v14M5 12h14"/>`
- trash: `<path d="M4 7h16M9 7V5h6v2M6 7l1 13h10l1-13"/>` 
- grip: two columns of dots: use circles or lines: `<path d="M9 6h.01M9 12h.01M9 18h.01M15 6h.01M15 12h.01M15 18h.01" stroke-linecap="round" stroke-width="2"/>` — with `stroke-width="2"` dots visible. Better use rects: `<circle cx="9" cy="6" r="1.2" fill="currentColor" stroke="none"/>` etc. I'll do circles.
- sun: circle + rays. moon: path.
- close: X path.
- warning: triangle + !.

Header theme toggle: icon + text label `LIGHT`/`DARK`.

Deck theme control: same button (second instance) with label `THEME · LIGHT`. I'll render both and sync.

Let's write the file.

Estimated length ~900 lines. Fine.

One more consideration: `justDragged` reset via setTimeout 100.

Also clicking card triggers sweep — on mobile tap too. ok.

Write.

For focus return after modal close: store `lastFocus`.

Alright, final code.

Also skip-link styling.

I'll write now for real.

CSS for board columns on mobile:
```css
@media (max-width:759px){
 .board{display:flex;overflow-x:auto;gap:16px;scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch;padding-bottom:8px}
 .column{flex:0 0 calc(100% - 40px);scroll-snap-align:start;min-height:440px}
 .column + .column::before{display:none}
}
```
`calc(100% - 40px)` shows ~40px of next column (16 gap + 24). Good.

Ensure `.board` doesn't cause body h-scroll: `max-width:100%`.

Desktop `.board{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:24px}`.

Also at 760–1023: columns can be narrower but still 3 across with 16px gaps. I'll add a media query reducing gaps/padding at <1100.

OK writing.