Forward Pass Animator
A step-by-step animator for the simplest possible model — a line y = m·x + b. The learner taps one of the input buttons, watches the x-slot fill, the multiply readout appear, the add readout appear, and the result settle. Every completed pass deposits a dot at (x, y) on a mini scatter plot. Once three dots exist, the underlying line of the model is revealed dashed-through behind them, driving home the insight that the model IS the line.
This is the canonical introduction to "what is a forward pass." The model is a function with two learnable parameters; training is what finds the m and b that fit the data — but before any of that, the learner needs to feel viscerally that the function is just doing arithmetic, repeatedly, deterministically.
Pick an input x. Watch the model compute its prediction step by step.
Installation
npx shadcn@latest add https://craftbits.dev/r/forward-pass-animator.jsonUsage
import { ForwardPassAnimator } from "@craft-bits/viz/forward-pass-animator";
<ForwardPassAnimator />Change the model:
<ForwardPassAnimator m={2} b={5} />Subscribe to compute events to drive a sibling chart or transcript:
<ForwardPassAnimator
onCompute={(history) => {
/* read history.map((h) => h.y) */
}}
/>Understanding the component
- Equation row. The model
y = [m] × [x] + [b]is rendered as three rounded slots. Themandbslots are pre-filled and stay static; thexslot starts holding the placeholder glyphxand is what the learner fills by tapping a button. - Tap an input. Tapping
x = ksnapskinto the x-slot, then plays a four-stage animation: slot-fill, multiply readout, add readout, settled result. Each stage is driven imperatively via motion'sanimate()so the SVG never re-renders per frame. - Phase machine. The animator classifies its own narrative phase from the history length:
observe(no computations yet),computing(one or two done),insight(three or more done — the line appears). The phase drives the accent colour of the narration paragraph and the dot-pulse callouts. - Scatter plot. Every completed pass deposits a dot at
(x, y)on the mini plot underneath the equation. After three dots, a dashed line tracesy = m·x + bacross the plot — the dots literally fall on it, which is the aha of the lesson. - Observe-phase breathing pulse. When no input has been picked yet, a small ring around the empty x slot breathes in and out so the eye lands on the next thing to do. The pulse disables under
prefers-reduced-motion: reduce. - Reduced motion. Under
prefers-reduced-motion: reduce, the four-stage animation collapses to an instant attribute set, the dot-pop spring becomes a snap, and the observe-phase pulse is suppressed.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
m | number | 3 | Slope of the model. |
b | number | 1 | Intercept of the model. |
xChoices | readonly number[] | [1, 2, 5, 10] | The set of input values exposed as tap-targets. |
transition | Transition | SPRINGS.snap | Override the per-stage animation transition. |
dotTransition | Transition | SPRINGS.bouncy | Override the dot-pop transition on the scatter plot. |
onCompute | (history) => void | — | Fires after each computation completes, with the full history. |
onReset | () => void | — | Fires when the user clicks Reset. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- The plot SVG is
role="img"with anaria-labelsummarising the model and the number of computations completed. - Every input button has a visible label and is reachable in tab order; focus shows a visible ring via
:focus-visible. - A live region below the buttons announces the most recent computation as plain prose so screen-reader users get the same outcome as sighted users.
- The narration paragraph also has
aria-live="polite"and reads as plain prose; it is the canonical explanation for each phase. - Colour is never the only signal — the phase is also encoded in the narration text and the live-region status.
- Motion respects
prefers-reduced-motion: reduce— the four-stage animation collapses to an instant attribute set, the dot-pop becomes a snap, and the observe-phase breathing pulse is suppressed.
Credits
- Extracted from:
craftingattention(app/src/lessons/primitives/math/ForwardPassAnimator.tsx). The source was a tightly bundled lesson component — it consumedSvgLabelfrom the SVG primitives andChallengeBtnfrom the lesson chrome, hardcoded the slope, intercept, and input choices to the canonicaly = 3x + 1walk-through, and depended on per-track lesson palette tokens. The viz extract drops the lesson chrome (raw<text>plus token-styled buttons), parameterisesm,b, andxChoicesso the animator works for any linear model, remaps the colour palette tovar(--cb-*)semantic tokens so consumer themes repaint freely, and re-keys the per-stage springs toSPRINGS.snapandSPRINGS.bouncyso all motion comes from the same place as every other craft-bits component.