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.
Step 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.

phase: observe · runs: 0
Customize
Math
5
0.9
Loop
8
600

Installation

npx shadcn@latest add https://craftbits.dev/r/warmup-reveal.json

Usage

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

  1. The math. With constant gradient g, Adam's first-moment EMA at step t is m_t = (1 − β₁ᵗ) · g. The bias-corrected estimate is m̂_t = m_t / (1 − β₁ᵗ), which equals exactly g for a constant gradient.
  2. Why it matters. Without correction, m₁ = 0.1 · g for the default β₁ = 0.9 — ten times too small. The optimiser barely moves on its first step.
  3. Why it stops mattering. The correction factor 1 − β₁ᵗ approaches 1 as t grows. By step 20 with β₁ = 0.9 it's 0.88, only a 14% adjustment.
  4. Step timeline. X-axis is integer steps from 1 to totalSteps. Y-axis is magnitude with a dashed reference line at y = gradient. The loop auto-advances one step per stepInterval ms.
  5. Dot pop animation. Each new dot pops in with SPRINGS.bouncy (overridable via transition). Under prefers-reduced-motion: reduce the pop collapses to instant.
  6. Narration phases. observedivergeconvergeinsight. The narration paragraph repaints with a tinted background per phase; onPhaseChange fires on every transition.
  7. Bias-correction toggle. Radix-style controlled + uncontrolled. Toggling fades the off trace to 30% opacity and brings the on trace to full strength.

Props

PropTypeDefaultDescription
gradientnumber5Constant gradient magnitude.
beta1number0.9Adam's first-moment decay rate.
totalStepsnumber8Number of steps to plot.
stepIntervalnumber600Milliseconds between auto-played steps.
autoPlaybooleantrueAuto-start the loop on mount.
biasCorrectionbooleanControlled toggle. Pair with onBiasCorrectionChange.
defaultBiasCorrectionbooleantrueInitial toggle state when uncontrolled.
onBiasCorrectionChange(next) => voidFires when the toggle changes.
onComplete() => voidFires once when the loop finishes a full pass.
onPhaseChange(phase) => voidFires on every narration-phase transition.
widthnumber520Width of the SVG viewBox.
heightnumber280Height of the SVG viewBox.
transitionTransitionSPRINGS.bouncyOverride the per-step dot-pop spring.
classNamestringMerged onto the root via cn().

Accessibility

  • The SVG is role="img" with an aria-label reporting the toggle state, step, and phase. The wrapping <div> is aria-labelledby an off-screen title.
  • The bias-correction control is a real <button> with aria-pressed and a visible :focus-visible ring. Play / Replay controls follow the same pattern.
  • A second aria-live="polite" sr-only region 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 the data-phase attribute 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 imported SvgLabel from the lesson chrome and ChallengeBtn from the construction primitives, hardcoded gradient = 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 drops SvgLabel and ChallengeBtn for raw <text> and a native <button> styled with the --cb-* semantic tokens, generalises gradient / beta1 / totalSteps / stepInterval / autoPlay into props, exposes a Radix-style controlled / uncontrolled biasCorrection API, surfaces onComplete and onPhaseChange, and lets callers override the per-step dot-pop spring via transition.