Personal blog. Static HTML generated from Markdown, served via GitHub Pages.
content/
about.md # section: about
assets/
tal-episodes.json # generated by scripts/scrape_tal_episodes.py
blog/
YYYYMMDD-title.md # section: blog (special — see below)
generator/
generate.py
input.css
requirements.txt
templates/
base.html
blog_index.html
post.html
section.html
tal_player.html # TAL shuffle player (no .md source — see TAL Player)
scripts/
scrape_tal_episodes.py # scrapes thisamericanlife.org, writes tal-episodes.json
docs/ # generated output — GitHub Pages source
pip install -r generator/requirements.txt
python generator/generate.py
Each .md file in content/ must have a section: field in its YAML front matter. The value determines where the output is written:
- Regular sections (
section: about,section: projects, etc.) — generatedocs/{section}/index.html, reachable at/{section}/. Add a new section by dropping a new.mdfile incontent/with the desiredsection:value. The nav bar is auto-generated:blogalways appears first, then sections in filename-alphabetical order, thental-playerlast. section: blog— special case. Must live incontent/blog/. Does not generate a section directory; see Blog below.
Files in content/ missing a section: field are silently skipped.
Blog posts require title: and date: (ISO format: YYYY-MM-DD) in addition to section: blog.
URL slug is derived deterministically as {date}-{slugified-title}, e.g. a post titled Hello World dated 2026-04-25 becomes /blog/2026-04-25-hello-world/.
Collision detection — if two posts would produce the same slug, the generator exits with an error listing both source files before writing anything.
Blog index is auto-generated, sorted newest-first, and served at both / and /blog/. There is no content/ source file for it.
Posts missing title: or date: are skipped with a warning.
A shuffle player for This American Life archival episodes, served at /tal-player/.
Episode data is stored in content/assets/tal-episodes.json and loaded client-side at runtime. The page itself is generated directly from generator/templates/tal_player.html with no .md source file.
Queue persistence — the playback queue (up to 100 episodes) and current position are saved to localStorage['tal-queue'] after every navigation. On page load the queue is restored and the current episode is shown paused at the beginning. This survives mobile browser tab discards (e.g. iOS backgrounding the tab for long enough that the OS kills the process).
To update the episode list:
.venv/bin/python scripts/scrape_tal_episodes.py
.venv/bin/python generator/generate.py
The scraper HEAD-checks every existing audio URL for reachability, then re-fetches all episode pages from thisamericanlife.org to get fresh playlist-data. Run periodically to pick up newly available episodes.
The .scrape-cache/ directory is a within-run deduplication cache and is wiped at the start of every scrape. It is not committed to the repo.
Point GitHub Pages to the docs/ folder on the main branch. Nav links use root-relative paths so they work correctly at the root domain.