Forty-three cities, four crime categories, FBI figures projected to 2025 — in one HTML file you can email to somebody.

Counting incidents ranks cities by population and tells you nothing. Everything here is per 100,000 residents, with the year-over-year change beside it so a bad year reads differently from a bad decade.
Tap a column header to re-sort. The map above follows whatever category is selected.
The brief was a dashboard someone non-technical could actually open. That ruled out a build step, a package manager, a backend, and any hosting decision at all.
A single .html. Double-click it. Works from a USB stick, an email attachment, or a folder on a desktop.
Leaflet from a CDN with tiles fetched live — the one thing that needs a connection, and it degrades to the ranked tables without it.
FBI city figures, baked into the file as a JSON literal and projected to 2025 with the FBI's national year-over-year trend. No fetch, no CORS, no API key to expire.
A composite: FBI rates 40%, dispatch density 20%, NeighborhoodScout 15%, CAP Index 15%, OpenCrime/SafeWise 10%. Dispatch figures are modeled from call volume, and labelled that way.
A sortable, filterable sidebar with colour-coded safety markers on the map. All state in memory — nothing to persist, nothing to configure.
Agencies opt in. A city that looks safe may simply have reported less completely that year.
Category boundaries shift between reporting standards, so long time series aren't clean comparisons.
Municipal boundaries carve metro areas arbitrarily. Two cities of the same name-recognition can cover wildly different footprints.
In smaller cities a handful of incidents swings the per-capita rate hard. Rank order at the bottom is noise, not signal.
A dashboard that hides these makes people confident about things they shouldn't be. This one puts them in the interface, not in a footnote nobody scrolls to.