feat(team): active border sweep, white chip, slower flow, gentle done
- Active card: a green highlight sweeps along the border left→right while the task runs (masked gradient ::before), signalling live work. - Status chip: white background (green/amber text), Paperclip-style. - Slower, more readable animation (edge 1500ms, stagger 680ms, longer flow gaps). - Arrival and task-completion are now soft glow fades with NO transform, so the card no longer twitches on done; a dedicated doneFade plays when a task the agent held goes done/cancelled. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
92a5b63954
commit
38bea3b594
@ -202,8 +202,8 @@ export function renderTeamHtml(cwd: string): string {
|
||||
/* Floating status chip that straddles the top-right edge (Paperclip style). */
|
||||
.node-badge { position: absolute; top: -9px; right: 12px; z-index: 3; font: 700 9.5px/1 var(--font-mono); text-transform: capitalize; letter-spacing: .02em; padding: 4px 9px; border-radius: 999px; }
|
||||
.node-badge:empty { display: none; }
|
||||
.node-badge.active { color: #0b3d1e; background: #7ee2a8; box-shadow: 0 2px 12px rgba(34,197,94,.4); }
|
||||
.node-badge.review { color: #3d2e07; background: #f0cf7a; box-shadow: 0 2px 12px rgba(210,153,34,.35); }
|
||||
.node-badge.active { color: #15803d; background: #ffffff; box-shadow: 0 2px 12px rgba(34,197,94,.45); }
|
||||
.node-badge.review { color: #7a5a12; background: #ffffff; box-shadow: 0 2px 12px rgba(210,153,34,.4); }
|
||||
|
||||
.node-info-btn { flex: 0 0 auto; width: 20px; height: 20px; border-radius: 50%; border: 1px solid var(--border); background: var(--raised); color: var(--muted); font: 700 11px/1 var(--font-mono); cursor: pointer; align-self: flex-start; transition: color 150ms, border-color 150ms; }
|
||||
.node-info-btn:hover { color: var(--text); border-color: var(--accent); }
|
||||
@ -219,10 +219,26 @@ export function renderTeamHtml(cwd: string): string {
|
||||
.st-link.review { color: var(--status-review); }
|
||||
.st-link:hover { text-decoration: underline; }
|
||||
|
||||
.node.active { border-color: rgba(34,197,94,.6); box-shadow: 0 0 0 1px rgba(34,197,94,.5), 0 0 24px rgba(34,197,94,.15); }
|
||||
/* Active card: a green highlight sweeps along the border L→R while the task runs. */
|
||||
.node::before {
|
||||
content: ''; position: absolute; inset: 0; border-radius: 14px; padding: 1.6px;
|
||||
background: linear-gradient(90deg, rgba(34,197,94,0) 32%, #22c55e 50%, rgba(34,197,94,0) 68%);
|
||||
background-size: 260% 100%; background-position: -50% 0;
|
||||
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
|
||||
mask-composite: exclude;
|
||||
opacity: 0; transition: opacity 340ms ease; pointer-events: none; z-index: 2;
|
||||
}
|
||||
.node.active { border-color: rgba(34,197,94,.4); box-shadow: 0 0 20px rgba(34,197,94,.10); }
|
||||
.node.active::before { opacity: 1; animation: borderSweep 2.6s linear infinite; }
|
||||
@keyframes borderSweep { 0% { background-position: -50% 0; } 100% { background-position: 150% 0; } }
|
||||
.node.reviewing { border-color: rgba(210,153,34,.55); box-shadow: 0 0 0 1px rgba(210,153,34,.45), 0 0 20px rgba(210,153,34,.12); }
|
||||
.node.arrive { animation: arrivePop 560ms ease; }
|
||||
@keyframes arrivePop { 0% { box-shadow: 0 0 0 0 rgba(34,197,94,.6); } 100% { box-shadow: 0 0 0 15px rgba(34,197,94,0); } }
|
||||
/* Gentle, non-jerky glow fades for pulse-arrival and task completion. */
|
||||
.node.arrive { animation: arriveGlow 700ms ease; }
|
||||
@keyframes arriveGlow { 0% { box-shadow: 0 0 0 1px rgba(34,197,94,.55), 0 0 18px rgba(34,197,94,.28); } 100% { box-shadow: 0 0 0 1px rgba(34,197,94,0), 0 0 0 rgba(34,197,94,0); } }
|
||||
.node.done-flash { animation: doneFade 900ms ease; }
|
||||
@keyframes doneFade { 0% { box-shadow: 0 0 0 1px rgba(34,197,94,.5); } 45% { box-shadow: 0 0 0 1px rgba(34,197,94,.4), 0 0 22px rgba(34,197,94,.22); } 100% { box-shadow: 0 0 0 1px rgba(34,197,94,0); } }
|
||||
|
||||
.edge-base { fill: none; stroke: var(--border); stroke-width: 1.5; animation: edgeBreath 4.5s ease-in-out infinite; }
|
||||
@keyframes edgeBreath { 0%,100% { opacity: .35; } 50% { opacity: .62; } }
|
||||
@ -257,7 +273,7 @@ export function renderTeamHtml(cwd: string): string {
|
||||
<script>
|
||||
(function() {
|
||||
var NS = 'http://www.w3.org/2000/svg';
|
||||
var SEG = 420, EDGE_DUR = 900;
|
||||
var SEG = 680, EDGE_DUR = 1500;
|
||||
var connDot = document.getElementById('conn-dot');
|
||||
var edgeMap = {}; // nodeId -> { fwd, rev } path from its parent
|
||||
var parentOf = {}; // nodeId -> parent nodeId
|
||||
@ -269,7 +285,13 @@ export function renderTeamHtml(cwd: string): string {
|
||||
function centerTop(el, box) { var r = el.getBoundingClientRect(); return { x: r.left - box.left + r.width/2, y: r.top - box.top }; }
|
||||
function centerBottom(el, box) { var r = el.getBoundingClientRect(); return { x: r.left - box.left + r.width/2, y: r.bottom - box.top }; }
|
||||
function nodeById(id) { return document.querySelector('.node[data-node-id="' + (id||'').replace(/"/g,'') + '"]'); }
|
||||
function flash(el) { if (!el) return; el.classList.remove('arrive'); void el.offsetWidth; el.classList.add('arrive'); setTimeout(function(){ el.classList.remove('arrive'); }, 560); }
|
||||
function flash(el) { if (!el) return; el.classList.remove('arrive'); void el.offsetWidth; el.classList.add('arrive'); setTimeout(function(){ el.classList.remove('arrive'); }, 700); }
|
||||
function doneFlashAgent(agent) {
|
||||
document.querySelectorAll('.node[data-agent="' + (agent || '').replace(/"/g, '') + '"]').forEach(function(n) {
|
||||
n.classList.remove('done-flash'); void n.offsetWidth; n.classList.add('done-flash');
|
||||
setTimeout(function(){ n.classList.remove('done-flash'); }, 900);
|
||||
});
|
||||
}
|
||||
|
||||
function layoutEdges() {
|
||||
var svg = document.getElementById('edges');
|
||||
@ -355,6 +377,7 @@ export function renderTeamHtml(cwd: string): string {
|
||||
if (t.status === 'in_progress' && was !== 'in_progress') pulseToAgent(agent, 'down');
|
||||
else if (t.status === 'review' && was !== 'review') pulseToAgent(agent, 'up');
|
||||
else if (t.status === 'open' && pv && t.assignedTo && t.assignedTo !== pv.assignedTo) pulseToAgent(agent, 'down');
|
||||
else if ((t.status === 'done' || t.status === 'cancelled') && (was === 'in_progress' || was === 'review')) doneFlashAgent(agent);
|
||||
});
|
||||
}
|
||||
var next = {}; tasks.forEach(function(t) { next[t.id] = { status: t.status, assignedTo: t.assignedTo }; });
|
||||
@ -487,7 +510,7 @@ export function renderTeamHtml(cwd: string): string {
|
||||
var s = DEMO[i];
|
||||
showCaption((i + 1) + '/' + DEMO.length + ' · ' + s.cap, s.rev);
|
||||
var dur = pulseBetween(s.from, s.to);
|
||||
await sleep(dur + 550);
|
||||
await sleep(dur + 850);
|
||||
}
|
||||
showCaption('');
|
||||
if (btn) btn.removeAttribute('disabled');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user