A skills graph that follows your scroll
The skills section on this site is a graph rather than a grid. The NASA PH emblem sits in the middle, one node per category sits on an ellipse around it, and curved links join them. As you scroll through the categories on the left, the matching node lights up on the right.
Laying it out on the server
The geometry is computed in the controller, not in the browser. Each category gets an angle on an ellipse, and from that a position and an SVG path from the hub's edge to the node's edge:
$angle = -M_PI / 2 + ($i * 2 * M_PI) / $count;
$x = $cx + $rx * cos($angle);
$y = $cy + $ry * sin($angle);
// A quadratic curve bent perpendicular to the straight line.
$path = sprintf('M%.1f %.1f Q%.1f %.1f %.1f %.1f', $sx, $sy, $qx, $qy, $ex, $ey);
Because the layout is data, adding a skill to the config adds a node, a link, and a step with no markup changes.
Drawing it in
anime.js v4 has a helper that turns SVG paths into drawable strokes. The reveal is a short timeline: the hub fades in, the links draw from the centre outward with a stagger, and the nodes pop in with a back-ease.
const tl = createTimeline({ defaults: { ease: 'outExpo' }, autoplay: false });
tl.add(hub, { opacity: [0, 1], scale: [0.8, 1], duration: 800 }, 0);
tl.add(createDrawable(links), { draw: ['0 0', '0 1'], duration: 900, delay: stagger(60) }, 150);
tl.add(nodes, { opacity: [0, 1], scale: [0.5, 1], ease: 'outBack', delay: stagger(60) }, 550);
Once the timeline finishes, a small circle is animated along every link on a loop using createMotionPath, which reads as data flowing through the stack.
Following the scroll
The graph is position: sticky, so it stays in view while the list of categories scrolls past. Deciding which category is current uses an IntersectionObserver with a root margin that shrinks the viewport to a thin band around its centre:
new IntersectionObserver(update, { rootMargin: '-45% 0px -45% 0px', threshold: 0 });
Whatever step crosses that band becomes current. Its node, link and pulse get an active class, and the chips inside the step pop in with a stagger. Hovering a node or a step highlights the pair as well, and clicking a node scrolls its step into the centre.
One thing that bit me
The first version triggered the graph's reveal with a scroll observer keyed to the graph's position in the document. Because the graph is sticky, that position was only true at the top of the section; jumping straight to a deep anchor left the nodes invisible. Switching the trigger to an IntersectionObserver on the rendered element fixed it, since that observer looks at where the element actually is.