Built a self-contained 1,766-line interactive HTML artifact
(course_materials/demos/differential-privacy-explainer.html)
teaching differential privacy fundamentals through an 8-act narrative
structure, plus a course-website wrapper page
(website/demos/differential-privacy-explainer/index.html).
All four planned interactive elements work correctly and have been
verified via Chrome automation against a local preview.
Deployment status: Dry-run succeeded. Real deploy deferred pending additional work in the iteration.
course_materials/demos/differential-privacy-explainer.html
(1,766 lines)Single self-contained HTML file with inline CSS and JavaScript. No build step, no framework, no external JS dependencies (only Google Fonts, matching the existing course demo pattern, with proper system-font fallbacks).
Structure:
<section class="act"> elements, one per
narrative act (~600 lines)<script> with datasets, utility functions,
and all interactive handlers (~660 lines)The 8 acts:
| Act | Title | Content | Interactive? |
|---|---|---|---|
| I | The Illusion of Invisibility | Incognito mode placebo hook | Static |
| II | Patient Zero | Latanya Sweeney / Governor Weld re-identification | Yes — live linkage attack |
| III | The Spartacus Strategy | k-anonymity and homogeneity attack | Yes — dataset transformation |
| IV | The Revolution | Differential privacy, frosted glass, epsilon budget | Yes — epsilon slider + histogram |
| V | DP in the Wild | Apple, Google, 2020 Census, SMPC | Static + external links |
| VI | The Dark Side | Minority erasure, Matthew effect, clipping | Yes — minority erasure visualization |
| VII | Law, Theater, and Trust | Quebec Law 25, privacy theater, trust gap | Static |
| VIII | The Reframe | Privacy as a dial, not a switch | Static |
Two side-by-side tables (Anonymized Medical Records / Cambridge Voter Registration) with 20 and 30 rows respectively. Three filter buttons progressively narrow the visible rows: birth date → gender → ZIP. The datasets were carefully crafted so that the filter sequence matches the podcast narrative exactly:
Verified in browser via automated filter sequence.
Three discrete buttons (k=1, k=3, k=5) that swap the dataset between raw, partially generalized, and fully generalized states. The data was designed so that at k=5:
The "Reveal Homogeneity Attack" button activates only at k=5 and highlights the vulnerable group with an explanation.
Verified: at k=5, exactly 5 rows show homogeneous Heart Disease in the first group, and the reveal button highlights them correctly.
The central pedagogical exhibit. An SVG histogram showing a synthetic salary distribution (10 bins, 1,000 people) with:
noise = -scale * sign(u) * ln(1 - 2|u|) where scale =
sensitivity/εbackdrop-filter: blur() overlay that scales
inversely with ε (the "frosted glass" visual motif from the
podcast)Verified across the full slider range:
A horizontal bar chart of a mock town with 5 demographic groups:
majority (950) + minorities (12, 7, 5, 3). Shares the same epsilon
slider pattern as Act IV. Reports
max(0, round(trueCount + laplaceNoise)) for each group.
When a noisy count reaches 0, the bar visually collapses to red and the
label gets a strikethrough with an "ERASED" annotation.
Verified via empirical re-roll testing:
The stats row reports the current erased count and updates on every slider change or re-roll.
website/demos/differential-privacy-explainer/index.html
(~230 lines)Course-website wrapper page following the exact pattern of the
existing data-control-vulnerability demo wrapper.
Includes:
../../course_materials/demos/differential-privacy-explainer.htmlProblem: Initial slider range was
min=-10 max=30, which at the top end mapped to ε=1000
(meaningless — no privacy at that level but also no practical utility
difference from ε=20).
Fix: Narrowed to min=-10 max=13, giving
a meaningful range of ε ≈ 0.1 to ε ≈ 20 that aligns with the plan's
specification and the pedagogical thresholds (0.5, 2, 10) used in the
interpretation text.
Problem: The plan document described the synthetic salary distribution as "mean=$55k" but the bin counts I used actually yield a true mean of $59,400. This is a plan-vs-code inconsistency, not a code bug.
Resolution: Left as-is. The artifact computes and displays the true mean dynamically, so students see the correct number. The plan's description was imprecise, but the demo is internally consistent.
Running against the checklist from plan.md:
| Criterion | Result |
|---|---|
| Artifact file exists, self-contained, has all 8 acts | Pass — 1,766 lines, all 8
<section class="act"> elements verified |
| All 4 interactive elements function correctly | Pass — all four verified in Chrome automation |
| Wrapper page exists and iframes the artifact | Pass — loads, correct title, correct iframe src, full course chrome |
| Narrative tracks the podcast progression | Pass — each act's copy lifts directly from the podcast arc extracted in the brainstorm phase |
| Loads in Chrome on macOS | Pass — verified via Chrome MCP |
| Loads in Safari on macOS | Not tested yet — Safari verification deferred to Verify phase |
| No console errors on load | Pass — read_console_messages returned
zero errors |
| No external JS dependencies | Pass — only Google Fonts CSS import (matches existing course demo pattern); zero JS libraries |
| Laplace noise computed client-side (verified via re-roll) | Pass — re-roll produces different values on every click |
| Epsilon slider produces visible, continuous changes | Pass — verified across 3 slider positions with quantitative output |
| Deployed to public website | Deferred — dry-run passed, real deploy held pending further iteration work |
| Synthetic/mock data only | Pass — all datasets are fabricated; the "William F. Weld" record is a historical reference with a public diagnosis known from the Sweeney 1997 incident |
Remaining verification work to do:
backdrop-filter which has had partial support historically;
confirm the frosted glass effect renders (or degrades gracefully) in
Safari