Agent Task Board
A tiny three-column board, To do, Doing and Done, with an AI agent working through it. Each task card glides across into Doing, a spinner turns and a progress bar fills along its bottom edge, then it glides on into Done and pops a green tick. The other cards shuffle up to fill the gap and the column counts tick over as they go.
- Read the brief
- Search for sources
- Draft the outline
- Write the summary
- Check the facts
How to use it
- Paste the snippet just before
</head>. - Add
data-agent-task-boardto an<ol>or<ul>. Each<li>is a task; they’re done in order.
Options
Add any of these attributes to the same element.
data-columns | The three column names, separated by |. Default To do|Doing|Done. |
data-step | How long each task takes. Default 1800 ms. A task can set its own. |
data-color | Colour of the progress bar and the working card. Default #7c3aed. |
data-loop | Put the cards back and work through them again. |
data-hold | How long the finished board shows before looping. Default 2400 ms. |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
(function () {
// Agent Task Board: a little three-column board (To do, Doing, Done). An AI
// agent picks up each task card in turn: the card glides into Doing, a
// progress bar fills with a spinner turning, then it glides on into Done and
// gets a green tick. Column counts tick over as cards move.
// Usage: <ol data-agent-task-board>
// <li>Read the brief</li><li>Draft the outline</li><li>Write the intro</li>
// </ol>
// Optional: data-columns (three column names separated by |, default
// "To do|Doing|Done"), data-step (ms each task takes, default 1800),
// data-color (progress and highlight colour, default #7c3aed), data-loop
// (put the cards back and work through them again), data-hold (ms the
// finished board shows before looping, default 2400). A task can set its own
// data-step.
// Script use: animacious.agentTaskBoard(el) runs it again.
var DEFAULTS = { columns: 'To do|Doing|Done', step: 1800, color: '#7c3aed', hold: 2400 };
var css =
'.atb{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:.6em;width:100%;margin:0;padding:0;list-style:none;text-align:left}' +
'.atb-col{display:flex;flex-direction:column;gap:.45em;min-width:0;padding:.55em;border-radius:.8em;' +
'background:color-mix(in srgb,var(--text,#1a1a2e) 4%,transparent);border:1px solid color-mix(in srgb,var(--text,#1a1a2e) 7%,transparent)}' +
'.atb-head{display:flex;align-items:center;gap:.4em;margin:0 0 .1em;font-size:.82em;font-weight:700;letter-spacing:.02em;' +
'color:color-mix(in srgb,var(--text,#1a1a2e) 65%,transparent);white-space:nowrap}' +
'.atb-head i{width:.55em;height:.55em;border-radius:50%;flex:none;background:#a1a1aa}' +
'.atb-col:nth-child(2) .atb-head i{background:var(--atb-c)}' +
'.atb-col:nth-child(3) .atb-head i{background:#16a34a}' +
'.atb-n{margin-left:auto;min-width:1.5em;padding:0 .4em;border-radius:99px;text-align:center;font-variant-numeric:tabular-nums;' +
'background:color-mix(in srgb,var(--text,#1a1a2e) 8%,transparent)}' +
'.atb-card{position:relative;display:flex;align-items:flex-start;gap:.45em;padding:.5em .6em;border-radius:.55em;overflow:hidden;' +
'background:var(--bg-card,#fff);color:var(--text,#1a1a2e);border:1px solid color-mix(in srgb,var(--text,#1a1a2e) 10%,transparent);' +
'box-shadow:0 1px 2px rgba(20,10,40,.06);line-height:1.3;transition:border-color .3s,box-shadow .3s,opacity .3s}' +
'.atb-card>span{min-width:0;overflow-wrap:break-word;hyphens:auto}' +
'.atb-mark{position:relative;flex:none;width:1em;height:1em;margin-top:.1em;border-radius:50%;border:.12em solid color-mix(in srgb,var(--text,#1a1a2e) 25%,transparent)}' +
'.atb-bar{position:absolute;left:0;bottom:0;height:.18em;width:100%;background:var(--atb-c);transform-origin:0 50%;transform:scaleX(0)}' +
'.atb-doing{border-color:color-mix(in srgb,var(--atb-c) 45%,transparent);box-shadow:0 4px 14px color-mix(in srgb,var(--atb-c) 22%,transparent)}' +
'.atb-doing .atb-mark{border-color:color-mix(in srgb,var(--atb-c) 25%,transparent);border-top-color:var(--atb-c);animation:atb-spin .7s linear infinite}' +
'.atb-done .atb-mark{border-color:#16a34a;background:#16a34a}' +
'.atb-done .atb-mark::after{content:"";position:absolute;left:28%;top:12%;width:28%;height:52%;border:solid #fff;border-width:0 .13em .13em 0;transform:rotate(45deg)}' +
'.atb-done>span{opacity:.6}' +
'@keyframes atb-spin{to{transform:rotate(360deg)}}';
var style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
function reduced() { return window.matchMedia('(prefers-reduced-motion: reduce)').matches; }
function esc(s) { return String(s).replace(/&/g, '&').replace(/</g, '<'); }
function setup(el) {
var d = el.dataset;
var names = (d.columns || DEFAULTS.columns).split('|');
var tasks = [].slice.call(el.children).map(function (li) {
return { text: li.textContent.trim(), step: +li.dataset.step || 0 };
});
el.classList.add('atb');
el.style.setProperty('--atb-c', d.color || DEFAULTS.color);
el.innerHTML = names.slice(0, 3).map(function (n) {
return '<li class="atb-col"><p class="atb-head"><i aria-hidden="true"></i>' + esc(n.trim()) +
'<span class="atb-n">0</span></p></li>';
}).join('');
var cols = [].slice.call(el.children);
var cards = tasks.map(function (t) {
var c = document.createElement('div');
c.className = 'atb-card';
c.innerHTML = '<i class="atb-mark" aria-hidden="true"></i><span>' + esc(t.text) + '</span><i class="atb-bar" aria-hidden="true"></i>';
c._atbStep = t.step;
cols[0].appendChild(c);
return c;
});
el._atb = { cols: cols, cards: cards, run: 0, timers: [], anims: [] };
counts(el._atb);
}
function counts(st) {
st.cols.forEach(function (col) {
col.querySelector('.atb-n').textContent = col.querySelectorAll('.atb-card').length;
});
}
// Move cards between columns and let every card glide from where it was to
// where it lands (FLIP: measure, move, then animate the difference away).
function move(st, changes, ms) {
var before = st.cards.map(function (c) { return c.getBoundingClientRect(); });
changes();
counts(st);
st.cards.forEach(function (c) {
if (c._atbA) c._atbA.pause();
anime.utils.set(c, { x: 0, y: 0 });
});
st.cards.forEach(function (c, i) {
var r = c.getBoundingClientRect();
var dx = before[i].left - r.left, dy = before[i].top - r.top;
if (Math.abs(dx) < 0.5 && Math.abs(dy) < 0.5) return;
c._atbA = anime.animate(c, { x: [dx, 0], y: [dy, 0], duration: ms || 560, ease: 'inOut(3)' });
st.anims.push(c._atbA);
});
}
function finishAll(st) {
st.cards.forEach(function (c) {
c.classList.remove('atb-doing');
c.classList.add('atb-done');
anime.utils.set(c.querySelector('.atb-bar'), { scaleX: 0 });
st.cols[2].appendChild(c);
});
counts(st);
}
function play(el) {
var st = el._atb, d = el.dataset;
var step = +d.step || DEFAULTS.step;
var hold = d.hold !== undefined ? +d.hold : DEFAULTS.hold;
var run = ++st.run;
st.timers.forEach(clearTimeout);
st.anims.forEach(function (a) { a.pause(); });
st.timers = [];
st.anims = [];
function later(fn, ms) { st.timers.push(setTimeout(function () { if (run === st.run) fn(); }, ms)); }
st.cards.forEach(function (c) { anime.utils.set(c, { x: 0, y: 0, scale: 1 }); });
if (st.cards.some(function (c) { return c.parentNode !== st.cols[0]; })) {
// Send everything back to To do in one sweep.
move(st, function () {
st.cards.forEach(function (c) { c.classList.remove('atb-done', 'atb-doing'); st.cols[0].appendChild(c); });
}, 700);
later(function () { next(0); }, 1100);
} else later(function () { next(0); }, 700);
function next(i) {
if (i >= st.cards.length) {
if (d.loop !== undefined) later(function () { play(el); }, hold);
return;
}
var c = st.cards[i];
var t = c._atbStep || step;
var bar = c.querySelector('.atb-bar');
anime.utils.set(bar, { scaleX: 0, opacity: 1 });
move(st, function () { c.classList.add('atb-doing'); st.cols[1].appendChild(c); });
st.anims.push(anime.animate(c, { scale: [1, 1.04, 1], duration: 560, ease: 'inOut(2)' }));
st.anims.push(anime.animate(bar, { scaleX: [0, 1], duration: t - 200, delay: 400, ease: 'inOut(1.4)' }));
later(function () {
move(st, function () {
c.classList.remove('atb-doing');
c.classList.add('atb-done');
st.cols[2].appendChild(c);
});
st.anims.push(anime.animate(bar, { opacity: 0, duration: 300, ease: 'out(2)' }));
st.anims.push(anime.animate(c.querySelector('.atb-mark'), { scale: [0.4, 1], duration: 500, delay: 300, ease: 'outBack(2.5)' }));
later(function () { next(i + 1); }, 520);
}, t + 400);
}
}
function init() {
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
if (!e.isIntersecting) return;
io.unobserve(e.target);
play(e.target);
});
}, { threshold: 0.2 });
document.querySelectorAll('[data-agent-task-board]').forEach(function (el) {
if (el._atb) return;
setup(el);
if (reduced()) finishAll(el._atb);
else io.observe(el);
});
}
window.animacious = window.animacious || {};
window.animacious.agentTaskBoard = function (el) { if (el && el._atb) play(el); };
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();
</script><ol data-agent-task-board>
<li>Read the brief</li>
<li>Search for sources</li>
<li data-step="3000">Draft the outline</li>
<li>Check the facts</li>
</ol>