FrankenPeanuts

Four random Peanuts panels from 1950 to 2000, stitched into a new strip on every reload.

Screenshot of FrankenPeanuts
Four panels from four different dates laid out as one strip. The last panel still has its original 8-24 date mark.

Peanuts ran from October 2, 1950 to February 13, 2000, every strip drawn by Charles Schulz alone. FrankenPeanuts picks four panels at random from the weekday strips and lays them out as a new four-panel comic. Most of them are incoherent. Some of them are accidentally better than they have any right to be. Hovering a panel shows its original date, and the strip is encoded in the URL hash, so a good one can be copied and sent to someone.

Cutting the strips up

My first version fetched whole strips from peanuts-search.com at page load and split them in the browser with Canvas pixel reads. It needed a CORS proxy, panels took seconds to appear, and some strips never loaded at all. So I moved the cutting to a one-off batch job and committed the results.

A Python script walks every non-Sunday date in the run (the Sunday strips have a different layout) and fetches the strip image. To find the gutters it samples rows from the middle of the image and counts the dark pixels in each column. A column where more than 70 percent of sampled pixels are dark is a gutter line. Runs of gutter columns collapse to their midpoint, and the midpoints become the cut positions. Slivers are dropped, and so is any panel whose width-to-height ratio is outside 0.55 to 1.45, because a very wide or very narrow panel looks wrong in a square grid.

The last run covered 15,456 dates, and 732 of them produced no usable panel, usually because the scan of a faded or crooked strip found no clean gutter. That's 55,357 JPEGs, about 1.1 GB, committed straight into the repo alongside a manifest.json that maps each date to its panel files. GitHub hasn't complained yet.

Running it on Actions

Fifty years of strips through one runner would take too long, so the workflow is a 10-job matrix, each job taking a 5-year slice of the archive with 16 fetch threads and a 50 ms pause per request. Each job uploads its partial manifest and its panels as artifacts. A merge job downloads all of them, combines the manifests, and commits the manifest and images back to the repo. The whole archive takes a few minutes. The script also skips any date already present in its output manifest, so an interrupted local run can be picked up.

The page loads the manifest, flattens it into a pool of 55,357 panels, picks four, and shows them in a grid, four across on desktop and two across under 600 px.