← 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 →