Send a node's emit pulse round the rim instead of scaling it out
The ring hung off the node itself, and `inset` on an absolute child resolves against the padding box — so on the brain's thick status ring it landed inside the outer edge and painted over the status the ring carries. Both node shapes now hang it off a border-less wrapper, where the same offsets clear the rim whatever border the node draws (measured: 4px on all four sides, on a 1px card border and a 7px neuron ring alike). The shape is a conic-gradient arc masked to the padding band, running one lap in `--duration-pulse` and going out on the way past. Note: BrainNode.tsx also carries an unrelated in-flight change from a concurrent session (the `seen` ref gating the pulse on a real emit); the two could not be separated in one file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -99,26 +99,66 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* A node that just published something says so, once, and settles. */
|
||||
/*
|
||||
* A node that just published something says so, once, and settles: an arc runs
|
||||
* one lap of the rim and goes out.
|
||||
*
|
||||
* The angle has to be registered to be animatable at all — an unregistered
|
||||
* custom property is a token stream, and swapping one for another mid-gradient
|
||||
* would step rather than sweep. Declared outside the guard below because it is
|
||||
* a type, not motion.
|
||||
*/
|
||||
@property --node-pulse-angle {
|
||||
syntax: "<angle>";
|
||||
inherits: false;
|
||||
initial-value: 0turn;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
/*
|
||||
* Hangs off the wrapper the node's border is *not* on: `inset` on an absolute
|
||||
* child resolves against the padding box, so a ring measured from the node
|
||||
* itself lands inside a thick border — the brain's status ring is one — and
|
||||
* paints over it. Measured from the wrapper, the same offsets clear the rim
|
||||
* whatever border the node draws. The wrapper carries the radius too, which
|
||||
* is what lets one rule fit both a rounded card and a circle.
|
||||
*
|
||||
* The band is the padding: the content box is cut back out of the mask, which
|
||||
* leaves a ring rather than the filled shape the gradient paints.
|
||||
*/
|
||||
.node-pulse {
|
||||
position: absolute;
|
||||
inset: -3px;
|
||||
inset: -4px;
|
||||
border-radius: inherit;
|
||||
border: 2px solid var(--primary);
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 18%, transparent);
|
||||
padding: 2px;
|
||||
pointer-events: none;
|
||||
animation: node-pulse var(--duration-pulse) var(--ease-emphasized) forwards;
|
||||
background: conic-gradient(
|
||||
from var(--node-pulse-angle),
|
||||
transparent,
|
||||
var(--primary) 90deg,
|
||||
transparent 90deg
|
||||
);
|
||||
mask:
|
||||
linear-gradient(#000 0 0) content-box exclude,
|
||||
linear-gradient(#000 0 0);
|
||||
animation: node-pulse var(--duration-pulse) linear forwards;
|
||||
}
|
||||
|
||||
/*
|
||||
* One lap at a constant rate, because the arc is travelling: an ease would
|
||||
* read as braking into the finish. The element stays mounted until the next
|
||||
* emit remounts it, so the lap has to put itself out on the way past.
|
||||
*/
|
||||
@keyframes node-pulse {
|
||||
from {
|
||||
--node-pulse-angle: 0turn;
|
||||
}
|
||||
75% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
to {
|
||||
--node-pulse-angle: 1turn;
|
||||
opacity: 0;
|
||||
transform: scale(1.09);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user