fix(team): kill done-twitch (glow on ::after) + only final node flashes
- Move the arrival/completion glow onto a dedicated ::after overlay animated by opacity only, so it no longer collides with the node's box-shadow transition — the card stays put on done (transform verified identity throughout). - A pulse now lights only its FINAL destination; waypoint nodes it merely passes through (e.g. the CEO on an architect→strategist peer handoff) are no longer flashed as 'involved'. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
b097ad71de
commit
55efae028a
@ -234,11 +234,16 @@ export function renderTeamHtml(cwd: string): string {
|
||||
.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); }
|
||||
/* 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); } }
|
||||
/* Arrival + completion glows live on a separate ::after overlay (opacity only)
|
||||
so they never fight the node's own box-shadow transition — no twitch. */
|
||||
.node::after {
|
||||
content: ''; position: absolute; inset: 0; border-radius: 14px; pointer-events: none;
|
||||
box-shadow: 0 0 0 1px rgba(34,197,94,.55), 0 0 22px rgba(34,197,94,.25);
|
||||
opacity: 0;
|
||||
}
|
||||
.node.arrive::after { animation: glowInOut 700ms ease; }
|
||||
.node.done-flash::after { animation: glowInOut 950ms ease; }
|
||||
@keyframes glowInOut { 0% { opacity: 0; } 38% { opacity: 1; } 100% { opacity: 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; } }
|
||||
@ -321,7 +326,7 @@ export function renderTeamHtml(cwd: string): string {
|
||||
});
|
||||
}
|
||||
|
||||
function firePulseEdge(nodeId, dir) {
|
||||
function firePulseEdge(nodeId, dir, doFlash) {
|
||||
var e = edgeMap[nodeId]; var svg = document.getElementById('edges');
|
||||
if (!e || !svg) return;
|
||||
var p = document.createElementNS(NS, 'path');
|
||||
@ -333,7 +338,9 @@ export function renderTeamHtml(cwd: string): string {
|
||||
p.style.strokeDashoffset = String(len + 20);
|
||||
var done = function() {
|
||||
try { p.remove(); } catch (_) {}
|
||||
flash(dir === 'up' ? nodeById(parentOf[nodeId]) : nodeById(nodeId));
|
||||
// Only the final destination lights up — waypoints (e.g. CEO on a
|
||||
// peer-to-peer handoff) are just passed through, not "involved".
|
||||
if (doFlash) flash(dir === 'up' ? nodeById(parentOf[nodeId]) : nodeById(nodeId));
|
||||
};
|
||||
if (p.animate) { p.animate([{ strokeDashoffset: len + 20 }, { strokeDashoffset: 0 }], { duration: EDGE_DUR, easing: 'cubic-bezier(.4,0,.5,1)' }).onfinish = done; }
|
||||
else setTimeout(done, EDGE_DUR);
|
||||
@ -344,7 +351,7 @@ export function renderTeamHtml(cwd: string): string {
|
||||
var chain = []; var cur = nodeId;
|
||||
while (cur && edgeMap[cur]) { chain.push(cur); cur = parentOf[cur]; } // node → topChild
|
||||
if (dir === 'down') chain.reverse(); // topChild → node
|
||||
chain.forEach(function(id, i) { setTimeout(function() { firePulseEdge(id, dir); }, i * SEG); });
|
||||
chain.forEach(function(id, i) { setTimeout(function() { firePulseEdge(id, dir, i === chain.length - 1); }, i * SEG); });
|
||||
}
|
||||
function pulseToAgent(agent, dir) {
|
||||
document.querySelectorAll('.node[data-agent="' + (agent||'').replace(/"/g,'') + '"]').forEach(function(n) {
|
||||
@ -478,7 +485,7 @@ export function renderTeamHtml(cwd: string): string {
|
||||
var seq = [];
|
||||
for (var u = 0; u < ai; u++) seq.push({ nodeId: A[u], dir: 'up' }); // from → LCA
|
||||
for (var d = Bidx[lca] - 1; d >= 0; d--) seq.push({ nodeId: B[d], dir: 'down' }); // LCA → to
|
||||
seq.forEach(function(step, k) { setTimeout(function(){ firePulseEdge(step.nodeId, step.dir); }, k * SEG); });
|
||||
seq.forEach(function(step, k) { setTimeout(function(){ firePulseEdge(step.nodeId, step.dir, k === seq.length - 1); }, k * SEG); });
|
||||
return Math.max(EDGE_DUR, seq.length * SEG + EDGE_DUR);
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user