Shadow Caster
A drag-to-discover visualisation of the geometric dot product a · b = |a||b| cos θ. Vector a lies fixed along the x-axis; the learner rotates a second vector b on a circle of radius |b| and watches the projection of b onto a's axis — the "shadow" — extend, vanish, and flip behind the origin as the angle changes.
Three ways to drive θ: drag the tip of b, drag the foot of the shadow along a's axis, or arrow-key the rotation. The dot product is rendered both as a tabular-numerics value under the shadow foot and as a sign-coloured wave-shaped band on the axis.
- θ
- 35°
- cos θ
- 0.82
- a · b
- 7.38
a · b = |a| · |b| · cos θ = 3.0 · 3.0 · cos(35°) = 7.38
Installation
npx shadcn@latest add https://craftbits.dev/r/shadow-caster.jsonUsage
import { ShadowCaster } from "@craft-bits/viz/shadow-caster";
<ShadowCaster />Controlled — own the angle of b:
import { useState } from "react";
import { ShadowCaster } from "@craft-bits/viz/shadow-caster";
function Demo() {
const [theta, setTheta] = useState(0.61);
return <ShadowCaster theta={theta} onThetaChange={setTheta} />;
}Override magnitudes when you want different a · b extrema:
<ShadowCaster magA={4} magB={2} />Hide the textual readouts and formula for a pure visual:
<ShadowCaster hideReadouts hideFormula />Understanding the component
- The plane and the two vectors. Centred 2D grid spanning
±5in math units with dashed axes. Vectorais drawn along the positive x-axis in the accent palette, vectorbrotates on a dashed arc of radius|b|in the warning palette. - The shadow. A drop-line falls from the tip of
bstraight down to a's axis. The segment from the origin to the foot of that drop-line is the shadow — its signed length is|b| cos θ. Under the segment, a sine-wave band fills withvar(--cb-success)(positive shadow) orvar(--cb-error)(negative shadow, behind the origin). - The dot-product readout. A monospaced tabular number tracks beneath the shadow foot. The value is tweened by
motion's imperativeanimate()onSPRINGS.snap(overridable viatransition) so the digit changes smoothly without re-rendering the SVG every frame. - The perpendicular indicator. When
|a · b|drops belowperpendicularThreshold(default0.15), a small right-angle marker appears at the origin in the success palette. The shadow has vanished —bis perpendicular toa. - Three drag handles. The tip of
b(rotation along the arc), the arc track itself (rotation by direct click), and the foot of the shadow (1-D drag along a's axis that projects back to an angle). All three arerole="slider"elements reachable via Tab. - Keyboard.
ArrowLeft/ArrowDowndecreaseθ;ArrowRight/ArrowUpincrease.Homeresets to0.Shifttriples the step. On the shadow-foot handle,ArrowLeft/ArrowRightmove the foot left/right along the axis with the angle following. - Reduced motion. Under
prefers-reduced-motion: reduce, every spring collapses to an instant attribute set.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
theta | number | — | Controlled angle of b in radians, CCW from a's axis. Pair with onThetaChange. |
defaultTheta | number | 0.61 | Initial angle when uncontrolled. |
onThetaChange | (next) => void | — | Fires on drag and arrow-key updates. |
magA | number | 3 | Length of vector a (fixed along x). |
magB | number | 3 | Length of vector b (arc radius). |
perpendicularThreshold | number | 0.15 | ` |
keyboardStepDeg | number | 5 | Per-arrow-press step in degrees. Shift triples. |
size | number | 320 | Side length (px) of the SVG viewBox. |
hideReadouts | boolean | false | Hide the θ / cos θ / a · b readout strip. |
hideFormula | boolean | false | Hide the formula line under the canvas. |
transition | Transition | SPRINGS.snap | Override the spring used by the dot-product readout. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- The SVG is
role="img"with anaria-labelreportinga's length,b's angle and length, and the current dot product. - All three drag targets (b-tip, arc track, shadow foot) are
role="slider"elements witharia-valuenow/aria-valuetext. Reachable via Tab, draggable with the pointer, movable with arrow keys (holdShiftfor3×step). - Colour is never the only signal: sign is encoded in the
data-sign="positive" | "zero" | "negative"attribute on the root and in the right-angle marker that springs in at perpendicular. - Focus state is rendered as a visible accent-toned cursor change on each drag handle.
- Motion respects
prefers-reduced-motion: reduce— the dot-product readout snaps to the new value rather than tweening.
Credits
- Extracted from:
craftingattention(app/src/lessons/primitives/math/ShadowCaster.tsx). The source was a lesson-bound component — it baked in three modes (explore / predict / challenge) wired to the lesson chrome, an in-component scoring system, and imports ofSvgLabel/ChallengeBtn/FeedbackBadge/ScoreDots/DoneCard/ModeStripfrom the project's lesson primitives. The viz extract drops every piece of mode UI and lesson chrome, generalisesmagA/magB/perpendicularThreshold/keyboardStepDeg/sizeinto props, exposes a Radix-style controlled/uncontrolledtheta+onThetaChangeAPI, removes the SVG-internalSvgLabelaxis labels, remaps every colour tovar(--cb-*)semantic tokens so consumer themes repaint freely, and replaces the source's non-existentSPRINGS.snappywithSPRINGS.snap.