Warmup Reveal
An auto-playing teaching visualisation for Adam's bias correction. Two overlaid traces on a step timeline: a dashed warn-toned raw exponential moving average that starts near zero and slowly climbs toward the true gradient, and a solid accent-toned bias-corrected estimate that lands at the true gradient immediately on step 1. Toggle bias correction to fade one trace and highlight the other; the narration crystallises why correction matters at the start, and why it stops mattering as steps accumulate.
Warmup reveal — Adam bias correction over a step timeline.
phase: observe · runs: 0Step 0 of 8. Bias correction on. Phase: observe. Raw m equals 0.50, corrected estimate equals 5.00. Correction factor 0.10.
Adam's m starts at zero. After one step, m = 0.1 × gradient — ten times too small.
Customize
Math
5
0.9
Loop
8
600
Installation
npx shadcn@latest add https://craftbits.dev/r/warmup-reveal.jsonUsage
import { WarmupReveal } from "@craft-bits/viz/warmup-reveal";
<WarmupReveal />Controlled bias-correction toggle:
import { useState } from "react";
import { WarmupReveal } from "@craft-bits/viz/warmup-reveal";
function Demo() {
const [biasOn, setBiasOn] = useState(true);
return (
<WarmupReveal
biasCorrection={biasOn}
onBiasCorrectionChange={setBiasOn}
/>
);
}Subscribe to phase changes and completion:
<WarmupReveal
onPhaseChange={(phase) => analytics.track("warmup-phase", { phase })}
onComplete={() => analytics.track("warmup-complete")}
/>Understanding the component
- The math. With constant gradient
g, Adam's first-moment EMA at steptism_t = (1 − β₁ᵗ) · g. The bias-corrected estimate ism̂_t = m_t / (1 − β₁ᵗ), which equals exactlygfor a constant gradient. - Why it matters. Without correction,
m₁ = 0.1 · gfor the defaultβ₁ = 0.9— ten times too small. The optimiser barely moves on its first step. - Why it stops mattering. The correction factor
1 − β₁ᵗapproaches 1 astgrows. By step 20 withβ₁ = 0.9it's0.88, only a 14% adjustment. - Step timeline. X-axis is integer steps from 1 to
totalSteps. Y-axis is magnitude with a dashed reference line aty = gradient. The loop auto-advances one step perstepIntervalms. - Dot pop animation. Each new dot pops in with
SPRINGS.bouncy(overridable viatransition). Underprefers-reduced-motion: reducethe pop collapses to instant. - Narration phases.
observe→diverge→converge→insight. The narration paragraph repaints with a tinted background per phase;onPhaseChangefires on every transition. - Bias-correction toggle. Radix-style controlled + uncontrolled. Toggling fades the off trace to 30% opacity and brings the on trace to full strength.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
gradient | number | 5 | Constant gradient magnitude. |
beta1 | number | 0.9 | Adam's first-moment decay rate. |
totalSteps | number | 8 | Number of steps to plot. |
stepInterval | number | 600 | Milliseconds between auto-played steps. |
autoPlay | boolean | true | Auto-start the loop on mount. |
biasCorrection | boolean | — | Controlled toggle. Pair with onBiasCorrectionChange. |
defaultBiasCorrection | boolean | true | Initial toggle state when uncontrolled. |
onBiasCorrectionChange | (next) => void | — | Fires when the toggle changes. |
onComplete | () => void | — | Fires once when the loop finishes a full pass. |
onPhaseChange | (phase) => void | — | Fires on every narration-phase transition. |
width | number | 520 | Width of the SVG viewBox. |
height | number | 280 | Height of the SVG viewBox. |
transition | Transition | SPRINGS.bouncy | Override the per-step dot-pop spring. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- The SVG is
role="img"with anaria-labelreporting the toggle state, step, and phase. The wrapping<div>isaria-labelledbyan off-screen title. - The bias-correction control is a real
<button>witharia-pressedand a visible:focus-visiblering. Play / Replay controls follow the same pattern. - A second
aria-live="polite"sr-onlyregion announces per-step numeric values so screen-reader users get the same insight. - The narration paragraph is
aria-live="polite". Phase is also encoded in thedata-phaseattribute on the root. - Colour is never the only signal: the two traces also differ in stroke style (dashed vs. solid).
- Motion respects
prefers-reduced-motion: reduce.
Credits
- Extracted from:
craftingattention(app/src/lessons/primitives/math/WarmupReveal.tsx). The source was a lesson-bound component — it importedSvgLabelfrom the lesson chrome andChallengeBtnfrom the construction primitives, hardcodedgradient = 5,beta1 = 0.9,totalSteps = 8, depended on per-track palette tokens (--color-accent-500,--color-warn-500,--color-success-500,--color-ink-*), and lived inside a lesson-only narration shell. The viz extract dropsSvgLabelandChallengeBtnfor raw<text>and a native<button>styled with the--cb-*semantic tokens, generalisesgradient/beta1/totalSteps/stepInterval/autoPlayinto props, exposes a Radix-style controlled / uncontrolledbiasCorrectionAPI, surfacesonCompleteandonPhaseChange, and lets callers override the per-step dot-pop spring viatransition.