All posts

Design

Most of the frame should be quiet

Draw every element at full strength and the frame comes out accurate and unreadable — a learner can't tell where to look. The thing I kept coming back to, lesson after lesson, was dull: pick one thing to be loud and let the rest go quiet.

May 11, 20265 min read

This is the failure I kept running into in educational animations, and it has nothing to do with the animation being wrong. Every element on screen is drawn at full strength: every settled node, every finished comparison, every part of the structure that won't be touched for ten more steps, all in saturated color at full size. The frame is busy, and busy reads as important. So the one cell the current step is actually about has to compete with the entire past and the entire future for your attention, and loses.

The frame is accurate. It's also unreadable, and an unreadable-but-accurate frame teaches worse than a slightly choppier one that's honest about where to look. A learner has one pair of eyes. Make ten things shout at once and they hear none of them clearly, and the correctness of everything they aren't looking at is just wasted.

I didn't start with a theory about this. I started annoyed. Lesson after lesson, the first cut would come out technically correct and impossible to read, and I'd sit there hunting for the thing it was supposed to be teaching. The move that kept working was dull: decide which single thing on the frame is the protagonist this beat, and make everything else step back. Not because the rest doesn't matter, but because this step isn't about it.

The piles I sort a frame into

Over enough lessons that hardened into a rough mental sort. Looking at a frame now, I drop everything on it into one of four piles, loudest to quietest:

  • The current thing — the compare, the write, the probe, the swap, the node being expanded this beat. It gets the budget: the saturated state color, the only real motion, the largest size. It's the answer to "what is happening right now."
  • The context that locates it — the active window, the sorted prefix, the path down a tree, the bucket being probed. It tells you where the current thing is. Normal weight, never louder than the current thing.
  • Everything inactive — history, finished subtrees, settled nodes, the parts of the structure this beat doesn't touch. This is the pile I kept forgetting, and forgetting it is the whole bug. It has to recede: lower opacity (somewhere around 0.3 to 0.6), thinner strokes, or just not drawn yet.
  • The explanation — the status line, the formula, the code highlight, the log. It narrates this beat and doesn't restate what the stage already shows.

There's nothing clever about the list. It's the order I should have been thinking in from the start, written down so I stop skipping the third pile, which is the one I always skip.

The check I run is just a look: pause on the busiest frame in the whole run and try to find the current thing. If it takes more than a second, the inactive pile isn't quiet enough yet. No measurement, no score, just the one-second look.

Color says what; weight says how loud

Once I started treating the inactive pile as something that had to recede, the tempting move was to turn those elements gray. Don't. Gray is already a word in this vocabulary; it means something. An empty slot is gray. An eliminated candidate is gray. Use gray a third time to mean "this is old" and you've collided three ideas onto one color, and now the learner can't tell empty from finished from faded.

So keep the two axes apart. Color is meaning — amber is comparing, green is matched, and a thing holds its color for as long as it means that. Weight is attention — opacity, stroke thickness, size. An eliminated cell that was being compared is still amber; you just draw it at 0.4 opacity. Fade with weight, never by reaching for color. That's the whole reason to keep them separate: when two states have to share a hue, weight is what keeps them apart.

The quietest version is not drawing it yet

The strongest way to push something into the inactive pile is to not draw it at all.

If the lesson is a recursion tree, the temptation is to lay the whole tree out on the first frame and use color to mark which node is active. This is the classic tree failure: the full tree at uniform weight is a wall of identical circles, and the active descent has to fight every one of them. Draw each node on the beat it's actually reached instead. Structure that doesn't exist yet this beat is the inactive pile at opacity zero. Same for a neural net's layers, a graph's frontier, a DP table's cells — draw them as the algorithm arrives, not before.

"Drawn but faded" and "not drawn yet" are both fine ways to be quiet. "Drawn at full strength before its beat" is not.

You've already seen this work

None of this is something I'm pitching from first principles; it's just what the lessons that read cleanly are already doing. Binary search fades the discarded half to about 0.45 opacity, lerped so the elimination is one smooth motion instead of a pop, and the range you've ruled out literally dims. Merge sort runs an opacity ladder across its current / child / ancestor tiers, so the active merge stays loud while the divide map above it drops to context weight. Gradient descent shrinks and fades past points to 0.4 so the current step is the only bright dot on the curve.

The move is the same every time: spend the contrast on one thing, and make the rest get out of its way.

It's beat pacing, in space

If this feels familiar, it should. There's a companion habit I lean on just as hard: a single beat does exactly one thing, one action per step, no fusing two state changes into one. That's the same idea applied to time. This is the same idea applied to space: one thing loud per frame. A legible animation wants both. Pace the changes so only one thing happens at a time, weight the frame so only one thing is loud at a time, and the learner's eye lands where the teaching is without being told.

Everything else can be quiet. Most of it should be.

See it: open binary search and watch the eliminated half dim as the range collapses — the part you've ruled out goes quiet so the part still in play stays loud.