XOR Puzzle

A focused one-screen puzzle. The learner drags three sliders — w₁, w₂, bias — and a glowing dashed line in (x₁, x₂) space pivots and shifts. Eight labelled points sit on the plot; six are easy, two are XOR-pocket intruders that no straight line can rescue. When the learner's best score reaches revealThreshold (default 7), a small reveal CTA appears. Clicking it pulses the two intruders and explains why no single boundary can place them both correctly — the natural intro to multi-layer networks.

XOR puzzle — drag the line to classify eight points.
Accuracy5/8
w₁0.50
w₂1.00
bias-1.50
Customize
Puzzle
7
0.50
1.00
-1.50

Installation

npx shadcn@latest add https://craftbits.dev/r/xor-puzzle.json

Usage

import { XORPuzzle } from "@craft-bits/viz/xor-puzzle";
 
<XORPuzzle />

Subscribe to state changes:

<XORPuzzle
  onStateChange={(s) => {
    /* read s.w1, s.w2, s.bias */
  }}
  onReveal={() => analytics.track("xor-revealed")}
/>

Pass your own dataset with custom intruder indices:

<XORPuzzle
  data={[
    { x1: 0.1, x2: 1.4, label: 1 },
    { x1: 2.2, x2: 0.6, label: 0 },
    // …
  ]}
  xorIndices={[0, 3]}
  revealThreshold={5}
/>

Understanding the component

  1. Coordinate system. The plot is a 300 × 300 SVG with origin in the top-left (SVG default). x₁ runs left-to-right; x₂ is flipped so positive x₂ reads as up visually. toSvg(v) maps a domain value to a pixel coordinate.
  2. The boundary line. The neuron decides class 1 whenever w₁·x₁ + w₂·x₂ + bias ≥ 0. The line where that expression equals zero is what we draw — clipped to the visible box by walking the four sides and keeping the first two intersections.
  3. The dataset. Eight points are arranged so that six are linearly separable but two — at indices 6 and 7 by default — form an XOR pocket: each one sits in the opposing class's territory.
  4. Best-score gating. A bestScoreRef tracks the highest score seen this session. The reveal CTA appears only when the learner has at some point hit revealThreshold — the discovery is rewarded, not gated behind the current adjustment.
  5. The reveal. Clicking the CTA pulses the two intruders with a 3-second <animate> ring (collapsed to a static outline under prefers-reduced-motion: reduce), labels them XOR, and expands the explanation panel underneath.
  6. Reduced motion. The pulsing intruder ring becomes a static dashed outline. The reveal-panel slide-in collapses to an instant swap.

Props

PropTypeDefaultDescription
defaultStateXORPuzzleState{ w1: 0.5, w2: 1, bias: -1.5 }Initial neuron parameters. Reset returns here.
dataReadonlyArray<XORPuzzleDataPoint>8-point demo setPoints to classify, each with x1, x2, label.
xorIndicesReadonlyArray<number>[6, 7]Indices into data that are highlighted as intruders when revealed.
revealThresholdnumber7Best score required before the reveal CTA appears.
onStateChange(state) => voidFires whenever w₁, w₂, or bias changes.
onReveal() => voidFires when the learner clicks "reveal".
classNamestringMerged onto the root via cn().

Accessibility

  • The plot SVG is role="img" with an aria-label summarising the current accuracy.
  • Each parameter slider is a native <input type="range"> with an aria-label carrying the label and live value — full keyboard support comes for free.
  • The reveal CTA and reset are real <button> elements with focus-visible rings.
  • Colour is never the only signal — the intruder pulse uses motion plus a XOR text label so colour-blind users see the cue.
  • Motion respects prefers-reduced-motion: reduce — every <animate> is gated and collapses to a static outline.

Credits

  • Extracted from: craftingattention (app/src/lessons/primitives/math/XORPuzzle.tsx). The source consumed lesson-track palette tokens (--color-accent-500, --color-ink-700, --color-fail-500, …), the SvgLabel helper, and a custom neuron-slider class. The viz extract swaps the palette to var(--cb-*) semantic tokens so consumer themes repaint freely, drops the SvgLabel helper for a plain <text> element, replaces the inline transition: { duration, ease } with SPRINGS.default / DURATIONS.default + EASINGS.out, and exposes the reveal threshold and intruder indices as props so the puzzle generalises beyond the bundled dataset.