Proof Machine

A single SVG canvas with two draggable 2D vectors, plus a horizontal "proof strip" of four computation nodes connected by + and =:

[a₁b₁] + [a₂b₂] = [Σ] = [|a||b|cos θ]

Geometric overlays — shadow bar (projection of b onto a), drop-line, component rectangles, projection foot, and angle arc — are always visible at a quiet baseline opacity, so spatial feedback stays primary during drag. Hovering or tapping a proof-strip node enhances the relevant overlay rather than revealing it from nothing. The sign of the dot product colors the shadow bar (success positive, error negative); crossing zero triggers a brief pulse so the perpendicular boundary feels like a real event. A prediction gate hides the sum for the first few interactions — once it opens, a construction mode asks you to stack the two shadow segments yourself, then auto-verifies against the geometric projection.

ba

6.0 + 6.0 = ?

a₁b₁ = 6.0, a₂b₂ = 6.0. Add them in your head — what's the sum? Tap "?" to check.
Customize
State
3
Layout
Geometry
320 px

Installation

npx shadcn@latest add https://craftbits.dev/r/proof-machine.json

Usage

import { ProofMachine } from "@craft-bits/viz/proof-machine";
 
<ProofMachine />

Controlled with external state:

const [vectors, setVectors] = useState({
  a: { x: 3, y: 2 },
  b: { x: 2, y: 3 },
});
 
<ProofMachine value={vectors} onValueChange={setVectors} />

Skip the prediction gate (geometry always visible, no construction challenge):

<ProofMachine gateThreshold={0} />

Understanding the component

  1. Math-space coordinate system. The plane shows x, y in [-range, +range] (default range = 5) with y pointing up. Vector tips clamp to a circle of radius 0.92 × range so arrowheads never clip the viewBox.
  2. Two draggable arrows. Each vector tip is an SVG circle with role="slider". Pointer events drive the math-space state directly; arrow-key nudges step 0.25 units (or 1 with Shift).
  3. Always-on geometric overlays. The signed shadow bar, the dashed drop-line from b to the projection foot, the component rectangles on each axis, and the angle arc all render at a quiet baseline opacity. The "active" proof-strip node enhances the matching overlay.
  4. Sign-aware shadow colour. Positive dot product paints the shadow bar with --cb-success; negative paints with --cb-error. Near-zero (perpendicular) lands on --cb-fg-muted. Crossing zero triggers a brief opacity flash.
  5. Prediction gate. The Σ and |a||b|cos θ nodes hide their values behind a "?" until you tap them. Each successful reveal increments a counter; once it reaches gateThreshold (default 3), the gate stays open and the construction challenge starts.
  6. Construction challenge. After the gate opens, the shadow bar hides and the a₁b₁ / a₂b₂ nodes pulse. Tap them in order to stack the two contributions; once both segments are placed, the geometry auto-confirms with a success-coloured projection foot and a check mark on Σ and |a||b|cos θ.
  7. Magnitude lock. The lock |v| pill freezes the current magnitudes of both vectors — drags now slide along arcs of constant length, isolating the angle as the only free variable.
  8. Animated dot-product readout. The numeric value in the formula tweens smoothly between renders; under prefers-reduced-motion, it snaps to the new value.

Props

PropTypeDefaultDescription
defaultValueProofMachineState{ a: {3,2}, b: {2,3} }Initial vector state (uncontrolled).
valueProofMachineStateControlled vector state. Pair with onValueChange.
onValueChange(next) => voidCalled whenever a vector moves.
rangenumber5Half-range of the visible axis.
sizenumber320Pixel side length of the square SVG canvas.
gateThresholdnumber3Reveals before the gate opens. 0 disables the gate.
rehideDelayMsnumber2000Delay before geometry re-hides after a check-flash (gate closed).
showControlsbooleantrueShow the preset / mag-lock / reset row.
showNarrationbooleantrueShow the live narration block.
presetTransitionTransitionSPRINGS.snapOverride the preset-tween transition.
classNamestringMerged onto the root via cn().

Accessibility

  • Each draggable vector tip is an SVG circle with role="slider", aria-label, aria-valuetext, and aria-valuemin / aria-valuemax so screen readers announce the current numeric position.
  • The canvas SVG carries a live aria-label summary ("Vector a equals … Vector b equals … Dot product … Angle … degrees").
  • The formula and narration blocks are both aria-live="polite" so updates are announced without interrupting other speech.
  • Every proof-strip node is a <button> with aria-pressed reflecting its active state and a descriptive aria-label that changes during the construction challenge to invite the next tap.
  • Keyboard support: arrow keys nudge the focused vector tip by 0.25 math-units; Shift+arrow nudges by 1.
  • Motion respects prefers-reduced-motion: reduce — the animated dot readout snaps, CSS transitions on overlays collapse to none, the sign-flip flash never triggers, and the preset tween skips to the final value.
  • Colour is never the only signal — the active node also bumps opacity and stroke-width, and runs a check-flash to confirm changes.

Credits

  • Extracted from: craftingattention (app/src/lessons/primitives/math/ProofMachine.tsx). The source was wired into lesson chrome via SvgLabel, lesson-specific --color-* tokens, and SPRINGS.snappy. The viz extract strips the lesson plumbing, swaps the palette to --cb-* semantic tokens, uses SPRINGS.snap for preset tweens, and exposes defaultValue / value / onValueChange for controlled use.