[object Object]

← back to Agent Abrams Kanban

Agent Abrams task-pipeline kanban: FLIP slides, timed auto-advance, neon UI

212c8d37f719b003cdeed76f8792f15a982822d7 · 2026-07-25 00:37:01 -0700 · Steve

Files touched

Diff

commit 212c8d37f719b003cdeed76f8792f15a982822d7
Author: Steve <steve@designerwallcoverings.com>
Date:   Sat Jul 25 00:37:01 2026 -0700

    Agent Abrams task-pipeline kanban: FLIP slides, timed auto-advance, neon UI
---
 .gitignore |   8 ++
 index.html | 370 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 378 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1924158
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+node_modules/
+.env*
+tmp/
+*.log
+.DS_Store
+dist/
+build/
+.next/
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..164cb33
--- /dev/null
+++ b/index.html
@@ -0,0 +1,370 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Agent Abrams — Task Pipeline</title>
+<style>
+:root{
+  --bg:#07070c;
+  --panel:rgba(18,18,28,0.55);
+  --card:rgba(32,33,46,0.92);
+  --ink:#e7e9f3;
+  --muted:#7d82a0;
+  --queued:#ff2e88;
+  --running:#00e5ff;
+  --done:#39ff8b;
+  --shipped:#ffb020;
+}
+*{box-sizing:border-box;margin:0;padding:0}
+html,body{height:100%}
+body{
+  background:
+    radial-gradient(1200px 700px at 15% -10%, rgba(255,46,136,0.10), transparent 60%),
+    radial-gradient(1000px 600px at 85% 110%, rgba(0,229,255,0.10), transparent 60%),
+    var(--bg);
+  color:var(--ink);
+  font-family:'Segoe UI',system-ui,-apple-system,Tahoma,sans-serif;
+  overflow:hidden;
+  height:100vh;
+}
+/* subtle animated grid */
+body::before{
+  content:"";position:fixed;inset:0;z-index:0;pointer-events:none;
+  background-image:linear-gradient(rgba(255,255,255,0.028) 1px,transparent 1px),
+                   linear-gradient(90deg,rgba(255,255,255,0.028) 1px,transparent 1px);
+  background-size:44px 44px;
+  mask-image:radial-gradient(circle at 50% 40%, #000 40%, transparent 85%);
+  animation:drift 24s linear infinite;
+}
+@keyframes drift{from{background-position:0 0,0 0}to{background-position:44px 44px,44px 44px}}
+
+.app{position:relative;z-index:1;height:100vh;display:flex;flex-direction:column;padding:16px 20px 20px}
+
+.topbar{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px}
+.brand{display:flex;align-items:center;gap:12px}
+.brand .pulse{
+  width:12px;height:12px;border-radius:50%;background:var(--running);
+  box-shadow:0 0 14px var(--running),0 0 4px #fff inset;animation:beat 1.6s ease-in-out infinite;
+}
+@keyframes beat{0%,100%{transform:scale(1);opacity:1}50%{transform:scale(1.35);opacity:.65}}
+.brand h1{font-size:1.02rem;font-weight:700;letter-spacing:2px;text-transform:uppercase}
+.brand span{color:var(--muted);font-weight:400}
+.stats{display:flex;gap:18px;font-size:.72rem;color:var(--muted);letter-spacing:1px;text-transform:uppercase}
+.stats b{color:var(--ink);font-variant-numeric:tabular-nums}
+
+.board{
+  flex:1;min-height:0;
+  display:grid;grid-template-columns:repeat(4,1fr);gap:16px;
+}
+.column{
+  display:flex;flex-direction:column;min-height:0;
+  background:var(--panel);
+  border:1px solid rgba(255,255,255,0.08);
+  border-radius:14px;
+  backdrop-filter:blur(10px);
+  box-shadow:0 10px 40px rgba(0,0,0,0.45);
+  overflow:hidden;
+}
+.column-header{
+  display:flex;justify-content:space-between;align-items:center;
+  padding:16px 16px 13px;
+  border-bottom:2px solid;
+  font-weight:700;text-transform:uppercase;letter-spacing:2px;font-size:.78rem;
+}
+.count{
+  min-width:26px;text-align:center;
+  background:rgba(255,255,255,0.08);
+  padding:3px 10px;border-radius:20px;font-size:.78rem;font-weight:700;
+  font-variant-numeric:tabular-nums;transition:transform .25s;
+}
+.count.bump{transform:scale(1.35)}
+.column-body{
+  flex:1;min-height:0;overflow-y:auto;overflow-x:hidden;
+  padding:12px;display:flex;flex-direction:column;gap:10px;
+}
+.column-body::-webkit-scrollbar{width:7px}
+.column-body::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.12);border-radius:4px}
+
+/* per-column neon accents */
+.col-queued{border-color:rgba(255,46,136,0.28)}
+.col-queued .column-header{color:var(--queued);border-color:var(--queued);text-shadow:0 0 12px rgba(255,46,136,.55)}
+.col-running{border-color:rgba(0,229,255,0.28)}
+.col-running .column-header{color:var(--running);border-color:var(--running);text-shadow:0 0 12px rgba(0,229,255,.55)}
+.col-done{border-color:rgba(57,255,139,0.28)}
+.col-done .column-header{color:var(--done);border-color:var(--done);text-shadow:0 0 12px rgba(57,255,139,.55)}
+.col-shipped{border-color:rgba(255,176,32,0.28)}
+.col-shipped .column-header{color:var(--shipped);border-color:var(--shipped);text-shadow:0 0 12px rgba(255,176,32,.55)}
+
+.card{
+  background:var(--card);
+  border:1px solid rgba(255,255,255,0.08);
+  border-left:3px solid var(--muted);
+  border-radius:10px;padding:11px 12px;
+  box-shadow:0 4px 10px rgba(0,0,0,0.35);
+  will-change:transform;
+}
+.card.enter{animation:pop .45s cubic-bezier(.2,.9,.25,1.2)}
+@keyframes pop{from{opacity:0;transform:translateY(-8px) scale(.96)}to{opacity:1;transform:none}}
+.card .row{display:flex;align-items:center;gap:9px}
+.avatar{
+  position:relative;width:26px;height:26px;border-radius:50%;flex-shrink:0;
+  display:grid;place-items:center;font-size:.62rem;font-weight:800;color:#06060b;
+  box-shadow:0 0 10px currentColor;
+}
+.title{
+  font-size:.82rem;font-weight:600;color:#fff;flex:1;
+  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
+}
+.meta{display:flex;align-items:center;justify-content:space-between;margin-top:9px;gap:8px}
+.agent{font-size:.66rem;color:var(--muted);letter-spacing:.4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
+.timer{
+  font-size:.72rem;color:var(--muted);
+  font-family:'SF Mono','Courier New',monospace;letter-spacing:.5px;font-variant-numeric:tabular-nums;
+}
+/* progress bar (running only) */
+.bar{height:3px;margin-top:9px;border-radius:3px;background:rgba(255,255,255,0.08);overflow:hidden;display:none}
+.bar>i{display:block;height:100%;width:0;border-radius:3px;background:var(--running);box-shadow:0 0 8px var(--running);transition:width .5s linear}
+
+.card.s-queued{border-left-color:var(--queued)}
+.card.s-running{border-left-color:var(--running);box-shadow:0 4px 16px rgba(0,229,255,0.14)}
+.card.s-running .bar{display:block}
+.card.s-running .avatar{animation:beat 1.4s ease-in-out infinite}
+.card.s-done{border-left-color:var(--done)}
+.card.s-shipped{border-left-color:var(--shipped);opacity:.82}
+.card.s-shipped .title::after{content:" ✓";color:var(--shipped)}
+
+@media (prefers-reduced-motion:reduce){
+  *{animation:none!important;transition:none!important}
+}
+</style>
+</head>
+<body>
+<div class="app">
+  <div class="topbar">
+    <div class="brand">
+      <div class="pulse"></div>
+      <h1>Agent Abrams <span>· Task Pipeline</span></h1>
+    </div>
+    <div class="stats">
+      <div>Uptime <b id="uptime">00:00</b></div>
+      <div>Shipped <b id="shipCount">0</b></div>
+      <div>Throughput <b id="tput">0.0</b>/min</div>
+    </div>
+  </div>
+
+  <div class="board" id="board">
+    <div class="column col-queued"  data-status="queued">
+      <div class="column-header"><span>Queued</span><span class="count" id="count-queued">0</span></div>
+      <div class="column-body" id="body-queued"></div>
+    </div>
+    <div class="column col-running" data-status="running">
+      <div class="column-header"><span>Running</span><span class="count" id="count-running">0</span></div>
+      <div class="column-body" id="body-running"></div>
+    </div>
+    <div class="column col-done"    data-status="done">
+      <div class="column-header"><span>Done</span><span class="count" id="count-done">0</span></div>
+      <div class="column-body" id="body-done"></div>
+    </div>
+    <div class="column col-shipped" data-status="shipped">
+      <div class="column-header"><span>Shipped</span><span class="count" id="count-shipped">0</span></div>
+      <div class="column-body" id="body-shipped"></div>
+    </div>
+  </div>
+</div>
+
+<script>
+(function(){
+  "use strict";
+  const STAGES = ['queued','running','done','shipped'];
+  const bodies = {
+    queued:  document.getElementById('body-queued'),
+    running: document.getElementById('body-running'),
+    done:    document.getElementById('body-done'),
+    shipped: document.getElementById('body-shipped'),
+  };
+  const counts = {
+    queued:  document.getElementById('count-queued'),
+    running: document.getElementById('count-running'),
+    done:    document.getElementById('count-done'),
+    shipped: document.getElementById('count-shipped'),
+  };
+
+  const TASK_NAMES = [
+    "Neural Training","Data Pipeline","Model Compile","API Gateway","Vector Index",
+    "Auth Service","Cache Layer","Log Analysis","Image Process","Text Embed",
+    "Schema Migrate","Feature Flags","Rate Limiter","Webhook Sync","Token Rotate",
+    "Batch Reindex","Fraud Scan","Report Gen"
+  ];
+  const AGENTS = [
+    {n:"Orion",  c:"#ff2e88"},{n:"Vega",   c:"#00e5ff"},{n:"Nova",  c:"#39ff8b"},
+    {n:"Atlas",  c:"#ffb020"},{n:"Lyra",   c:"#bd6bff"},{n:"Rigel", c:"#22d3ee"},
+    {n:"Cassia", c:"#ff5c5c"},{n:"Draco",  c:"#7cff5c"},{n:"Mira",  c:"#ff8ad4"},
+    {n:"Phoenix",c:"#ffd23f"}
+  ];
+  // stage dwell windows in ms [min,max]
+  const DWELL = { queued:[2500,6500], running:[4000,9000], done:[2000,5000], shipped:[5000,9000] };
+
+  const rint = (a,b)=>a+Math.floor(Math.random()*(b-a+1));
+  const pick = a=>a[Math.floor(Math.random()*a.length)];
+  const now  = ()=>performance.now();
+
+  let uid = 0, shippedTotal = 0;
+  const startWall = Date.now();
+  const tasks = [];
+
+  function makeTask(seedStage){
+    const agent = pick(AGENTS);
+    const t = {
+      id: ++uid,
+      title: pick(TASK_NAMES)+"-"+rint(100,999),
+      agent,
+      status: seedStage || 'queued',
+      createdAt: now(),
+      nextAt: now() + rint(...DWELL[seedStage||'queued']),
+      el: null,
+    };
+    t.el = buildEl(t);
+    tasks.push(t);
+    bodies[t.status].appendChild(t.el);
+    t.el.classList.add('enter');
+    t.el.addEventListener('animationend',()=>t.el.classList.remove('enter'),{once:true});
+    return t;
+  }
+
+  function buildEl(t){
+    const el = document.createElement('div');
+    el.className = 'card s-'+t.status;
+    const initials = t.agent.n.slice(0,2).toUpperCase();
+    el.innerHTML =
+      '<div class="row">'+
+        '<div class="avatar" style="background:'+t.agent.c+';color:'+t.agent.c+'">'+
+          '<span style="color:#06060b">'+initials+'</span>'+
+        '</div>'+
+        '<div class="title">'+t.title+'</div>'+
+      '</div>'+
+      '<div class="bar"><i></i></div>'+
+      '<div class="meta">'+
+        '<span class="agent">◈ '+t.agent.n+'</span>'+
+        '<span class="timer">00:00</span>'+
+      '</div>';
+    return el;
+  }
+
+  function fmt(ms){
+    const s=Math.floor(ms/1000);
+    const m=Math.floor(s/60), ss=s%60;
+    if(m>=60){const h=Math.floor(m/60);return String(h).padStart(2,'0')+':'+String(m%60).padStart(2,'0')+':'+String(ss).padStart(2,'0');}
+    return String(m).padStart(2,'0')+':'+String(ss).padStart(2,'0');
+  }
+
+  // ---- FLIP: smooth slide when a card changes column ----
+  function advance(t){
+    const i = STAGES.indexOf(t.status);
+    if(i < STAGES.length-1){
+      moveWithFlip(t, STAGES[i+1]);
+    } else {
+      // shipped -> recycle back to queued (keeps ~constant board population)
+      shippedTotal++;
+      t.title = pick(TASK_NAMES)+"-"+rint(100,999);
+      t.agent = pick(AGENTS);
+      t.createdAt = now();
+      const initials = t.agent.n.slice(0,2).toUpperCase();
+      t.el.querySelector('.avatar').style.background = t.agent.c;
+      t.el.querySelector('.avatar').style.color = t.agent.c;
+      t.el.querySelector('.avatar span').textContent = initials;
+      t.el.querySelector('.title').textContent = t.title;
+      t.el.querySelector('.agent').textContent = '◈ '+t.agent.n;
+      moveWithFlip(t, 'queued');
+    }
+    // status was set inside moveWithFlip; schedule its next hop
+    t.nextAt = now() + rint(...DWELL[t.status]);
+  }
+
+  function moveWithFlip(t, toStage){
+    // FIRST: record all card rects
+    const first = new Map();
+    tasks.forEach(o=>first.set(o.id,o.el.getBoundingClientRect()));
+
+    // MOVE in the DOM
+    t.status = toStage;
+    t.el.className = 'card s-'+toStage;
+    bodies[toStage].appendChild(t.el);
+    const bar = t.el.querySelector('.bar>i'); if(bar) bar.style.width='0';
+
+    // LAST + INVERT + PLAY for every affected card
+    tasks.forEach(o=>{
+      const f = first.get(o.id); if(!f) return;
+      const l = o.el.getBoundingClientRect();
+      const dx = f.left - l.left, dy = f.top - l.top;
+      if(dx || dy){
+        o.el.style.transition='none';
+        o.el.style.transform='translate('+dx+'px,'+dy+'px)';
+        requestAnimationFrame(()=>{
+          o.el.style.transition='transform .6s cubic-bezier(.4,0,.2,1)';
+          o.el.style.transform='';
+        });
+      }
+    });
+
+    bumpCount(toStage);
+  }
+
+  function bumpCount(stage){
+    const c = counts[stage];
+    c.classList.add('bump');
+    setTimeout(()=>c.classList.remove('bump'),260);
+  }
+
+  // ---- ticks ----
+  function tickAdvance(){
+    const t0 = now();
+    // advance at most a couple per tick so slides don't collide visually
+    let moved = 0;
+    for(const t of tasks){
+      if(t0 >= t.nextAt && moved < 2){
+        advance(t); moved++;
+      }
+    }
+    // refresh column counts
+    STAGES.forEach(s=>{
+      const n = tasks.filter(t=>t.status===s).length;
+      if(counts[s].textContent != n) counts[s].textContent = n;
+    });
+  }
+
+  function tickSecond(){
+    const t0 = now();
+    for(const t of tasks){
+      const tm = t.el.querySelector('.timer');
+      if(tm) tm.textContent = fmt(t0 - t.createdAt);
+      if(t.status==='running'){
+        const bar = t.el.querySelector('.bar>i');
+        if(bar){
+          // progress = elapsed since entering running / its dwell window (approx via nextAt)
+          const total = DWELL.running[1];
+          const remain = Math.max(0, t.nextAt - t0);
+          const pct = Math.min(100, Math.max(4, 100*(1 - remain/total)));
+          bar.style.width = pct+'%';
+        }
+      }
+    }
+    // header stats
+    document.getElementById('uptime').textContent = fmt(Date.now()-startWall);
+    document.getElementById('shipCount').textContent = shippedTotal;
+    const mins = (Date.now()-startWall)/60000;
+    document.getElementById('tput').textContent = mins>0 ? (shippedTotal/mins).toFixed(1) : '0.0';
+  }
+
+  // ---- seed ~10 cards spread across the pipeline ----
+  const seed = ['queued','queued','queued','running','running','running','done','done','shipped','shipped'];
+  seed.forEach((s,i)=>{
+    setTimeout(()=>{ makeTask(s); STAGES.forEach(st=>counts[st].textContent = tasks.filter(t=>t.status===st).length); }, i*90);
+  });
+
+  setInterval(tickAdvance, 700);
+  setInterval(tickSecond, 500);
+})();
+</script>
+</body>
+</html>

(oldest)  ·  back to Agent Abrams Kanban  ·  Add interactive controls (pause/speed/spawn/click-advance) + 3a5fb88 →