Area Collapse

A 2x2 determinant visualizer you can grab with your hands. Two draggable column-vector tips form a parallelogram on a Cartesian grid; the parallelogram's area equals the determinant of the matrix whose columns are those vectors. As the columns rotate toward each other the area shrinks. When they become parallel the determinant hits zero, the parallelogram collapses onto a dashed line, and the SINGULAR badge lands. Drag past that line and the determinant flips sign — the matrix mirrors the plane.

The viz is uncontrolled — initialCol0 and initialCol1 seed the starting state, and onColumnsChange lets the caller mirror the live values into a surrounding UI.

|det| = 4col 0col 1
det A = (a)(d) (b)(c)
= (2)(2) (1)(0)
= 4
Drag either column tip — col 0 or col 1 — and bring them toward each other. The parallelogram measures how much the matrix spreads space.
Customize
Canvas
320 px
±4
Column 0
2
0
Column 1
1
2
Story

Installation

npx shadcn@latest add https://craftbits.dev/r/area-collapse.json

Usage

import { AreaCollapse } from "@craft-bits/viz/area-collapse";
 
<AreaCollapse
  initialCol0={{ x: 2, y: 0 }}
  initialCol1={{ x: 1, y: 2 }}
  size={320}
/>

Read the live columns out as the learner drags:

<AreaCollapse
  initialCol0={{ x: 2, y: 0 }}
  initialCol1={{ x: 1, y: 2 }}
  onColumnsChange={(col0, col1) => {
    console.log("det =", col0.x * col1.y - col1.x * col0.y);
  }}
/>

Drop the singularity dramatics for a calmer parallelogram explorer:

<AreaCollapse showSingularBadge={false} />

Understanding the component

  1. Two draggable column tips. col0 (accent / purple) and col1 (warning / orange) each render as a thick line from the origin to an invisible 14px hit-target circle on the tip. Drag either tip in any direction; the parallelogram updates every frame.
  2. One parallelogram, two layers. A filled polygon and a stroked outline cover the same four corners — the origin, col0, col0 + col1, and col1. The fill's opacity scales with the magnitude of the determinant so a small parallelogram fades naturally, with a 0.04 opacity floor so it never disappears completely.
  3. Five narration states. wide (success tone), shrinking (accent), thin (warning), singular (error tone, locks once entered), negative (error tone, mirror language). Each state drives both the parallelogram fill colour and the narration paragraph under the canvas.
  4. The singular climax. When the determinant hits the singular threshold, a dashed line replaces the parallelogram, the SINGULAR badge animates in on SPRINGS.bouncy, and the narration locks to the singular copy until reset.
  5. Det formula scrubber. Below the canvas a colour-coded det A = ad − bc formula updates in real time. Column 0 entries inherit the accent colour; column 1 entries inherit the warning colour; the final value flips between cb-success, cb-warning, and cb-error depending on sign and magnitude.
  6. Disk-clamped drag. Tips are clamped to a disk of radius 0.9 * range, so the parallelogram cannot escape the canvas. Pointer capture is set on pointer-down so the drag survives leaving the SVG.
  7. Reduced motion. When prefers-reduced-motion: reduce is set, the SINGULAR badge enter and exit collapse to instant — the badge still appears and disappears, just without the spring overshoot.

Props

PropTypeDefaultDescription
initialCol0{ x, y }{ x: 2, y: 0 }Starting position of the accent (purple) column vector tip.
initialCol1{ x, y }{ x: 1, y: 2 }Starting position of the warning (orange) column vector tip.
rangenumber4Half-range of both axes. The canvas covers [-range, range].
sizenumber320Pixel size of the square SVG canvas.
showSingularBadgebooleantrueShow the SINGULAR badge plus dashed collapse line when the determinant is near zero.
transitionTransitionSPRINGS.bouncyTransition for the SINGULAR badge enter and exit.
onColumnsChange(col0, col1) => voidNotified whenever the user moves a column.
classNamestringMerged onto the root via cn().

Accessibility

  • Each drag handle is a role="slider" SVG circle with aria-label ("Drag column 0" / "Drag column 1") and aria-valuetext reporting the live (x, y) of the tip.
  • The SVG element exposes role="img" with an aria-label summary that names both columns and the current determinant.
  • The narration block is aria-live="polite" — state-change copy is announced without stealing focus.
  • The root element exposes data-narr-state (wide / shrinking / thin / singular / negative) for downstream styling or assistive hooks.
  • Colour is never the only signal — every state is reinforced by the narration copy, the formula colour, and (in the singular case) the SINGULAR badge plus the dashed collapsed line.
  • Drag handles have a 14px hit radius (28px diameter) — meets the Fitts' Law guideline when the surrounding viz padding is included.
  • Motion respects prefers-reduced-motion: reduce — the SINGULAR badge transition collapses to instant.

Credits

  • Extracted from: craftingattention (src/lessons/primitives/math/AreaCollapse.tsx). The source was a determinants and singularity lesson primitive, hardcoded to a 320px canvas, the lesson's --color-ink / --color-success / --color-accent / --color-warn / --color-fail palette, and the project's ca-narration styled paragraph. The viz extract repaints onto --cb-accent / --cb-warning / --cb-success / --cb-error / --cb-fg-* so it themes alongside every other craft-bits component, exposes size / range / initialCol0 / initialCol1 / onColumnsChange / showSingularBadge for reuse outside the lesson, and swaps framer-motion's m namespace for the canonical motion import.