BAYES — Guide: how the interactive false-positive area diagram was designed and built

BAYES
Colophon · how it was made

BAYES turns the classic false-positive puzzle into a living proportional-area diagram. A population square splits by how rare a condition is; a test with known sensitivity and specificity partitions it into true and false results. Drag the base rate and the areas re-flow — exposing that a 99%-accurate screen, run on something rare, produces mostly false alarms.

01 The concept

One fictional teaching instrument, one job: make Bayes' theorem felt rather than derived.

The audience is anyone who has ever been handed a frightening positive result — patients, students, the numerate-but-uneasy. The single job of the page is to show that the meaning of a positive test depends less on the test's accuracy than on the base rate: how common the thing being tested for actually is. The counter-intuition — most positives are false — is placed on screen at rest, at a rare 0.30% base rate, so the shock lands before you touch anything.

02 Palette

A neutral paper ground — this is a document, a ledger of people, not a screen effect. Two arguable accents: evidence blue for what is true, alarm red for what is false. The line/mark colours stay saturated for strokes and area fills; body copy runs in a dark slate ink that clears 4.5:1 on paper.

Paper
#EFEAE0
The population ground — calm, papery, un-clinical.
Evidence blue
#2E6EA6
True positives, strokes, the posterior number — the truth the test catches.
Alarm red
#C24A3A
False positives — the crowd of healthy people wrongly flagged.
Paper-3
#DED7C8
True negatives — the quiet, correctly-cleared majority.
Slate ink
#20242A
Body copy — a derived dark ink, not the blue mark (contrast ≥ 9:1).
Pale evidence
#B7CBDD
False negatives — sick, but missed. A muted, quietly ominous tint.

03 Type

Newsreader is the voice — an optical-size serif with the composure of a broadsheet and a genuinely expressive italic, used for the headline and the big posterior figure. IBM Plex Mono carries every number, label and axis: the ledger register that makes the counts feel measured, not decorative.

Newsreader · display
A test right ninety-nine times
Newsreader · italic
Belief, re-weighed by evidence.
IBM Plex Mono
P(sick | +) = 22.9%

04 Technique & the signature

No libraries, no canvas, no raster — the whole instrument is inline SVG driven by a small state machine. The population square is four <rect>s whose x / width / y / height are recomputed every frame from three numbers: base rate p, sensitivity Se, specificity Sp.

SignatureThe proportional-area re-flow. Drag the base rate and the sick column narrows to a hairline while the red false-positive band swells — the "positives" bar beside it re-normalises, and the posterior P(sick | +) is re-derived live from the same numbers.
GeometryColumn split at x = p·200; each column's top band is the "tested positive" region (Se for the sick, 1−Sp for the healthy). True proportions kept — the sick sliver really is a hairline.
Base-rate scaleThe slider is logarithmic (1 in 10,000 → 1 in 3) so a whole world of rarity fits one throw, and the sweep reads evenly.
MotionA single rAF loop lerps state toward target; no library. On load it sweeps from a common 8% down to the resting 0.30%, then hands control to the slider and stops.
Resting stateRendered statically first, at 0.30% base rate, so the "23% — most alarms false" shock survives even if fonts or JS are slow, and reads in a still thumbnail.
Guardsprefers-reduced-motion snaps to the settled frame (CSS + matchMedia); loop pauses on document.hidden; :focus-visible on every control; SVG scales fluidly, so resize is free.

05 Iteration log

Pass 1 Craft

  • Found the hero headline styling targeted h1 descendants, but the visible headline is a decorative div (the real h1 is visually-hidden for a11y). Result: the "Bayes" kicker collapsed inline into the sentence and the italic "ninety-nine" lost its blue. Retargeted to a .herotitle class — kicker now sits above, italic reads evidence-blue.
  • Control labels were inline spans, so on narrow widths the label name and its value ran together on one line. Set name and value to block — clean two-line stack at every width.
  • Tuned the false-positive sub-line and legend counts to update from the same rounded integers as the diagram, so the copy and the areas can never disagree.

Pass 2 Depth

  • Added a legend ↔ region micro-interaction: hovering a legend row dims the square and lifts its matching region, so you can pick out the hairline true-positive sliver by name.
  • Slowed the opening sweep (a gentler lerp constant while the intro runs) so the red false-positive band visibly floods as rarity increases, then eases to rest — the concept, demonstrated, before you touch it.
  • Added the live Bayes derivation beneath the verdict — the same numerator and denominator, recomputed every frame — so the area and the algebra are visibly one thing.

Pass 3 Hardening

  • Verified the 375px layout: figure stacks, the base-rate control spans full width, nav and wordmark sit inside the viewport (no overflow, clean scrollWidth).
  • Confirmed the reduced-motion path renders the static 0.30% resting frame with zero console errors — no blank, no waiting.
  • Chanel rule — removed an ornament: the population grid was a busy 20×20 (400 cells) reading as graph paper. Cut to a fainter 10×10 so the crisp area partition leads and the texture only whispers.
  • Final copy read; checked focus rings on all three sliders and the replay control; headless-drove the base-rate slider and asserted the areas, bar and posterior all re-flow.