A passphrase-gated, Matrix-styled static site that follows one word through every episode of a YouTube show. Every point on the page links to the exact second on YouTube. Which show, and which word, are not written anywhere in this repository.
The site lives in docs/ and is served by GitHub Pages. Everything readable is
encrypted in the browser's favour: the data, the transcripts and the search index
ship only as AES-256-GCM ciphertext under a key derived from the passphrase.
scripts/fetch_captions.shreadswork/source.env(channel URL, playlist, title keyword, display names), lists the channel, picks the episodes (scripts/select_episodes.py), and downloads English captions withyt-dlp.scripts/build_data.pyscans the captions for a word and writes a payload towork/.scripts/build_search.pychunks every transcript into timestamped passages andscripts/embed_search.mjsembeds them with MiniLM (transformers.js in Node) intowork/search/. This powers the terminal'sfindcommand. Needscd scripts && npm installonce.scripts/encrypt_site.mjsencrypts all of that (PBKDF2 over the passphrase, then AES-256-GCM) intodocs/enc/. Only ciphertext is committed.docs/index.htmlasks for the passphrase, decrypts in the browser, and renders the charts and the terminal. No build step for the page itself.
work/ is gitignored and holds every secret and every plaintext intermediate:
work/passphrases.txt, work/source.env, work/subjects*.json, work/subject_extractor.js, captions, and data.
brew install yt-dlp # or pipx install yt-dlp
./scripts/fetch_captions.sh
git commit -am "Refresh data" && git pushThe download is deliberately slow (a few seconds per video) because YouTube
rate-limits the caption endpoint. Re-running the script skips captions that are
already in work/subs/, so a refresh only fetches new episodes.
The header is an interactive shell. help lists commands. find does semantic
search in the browser with a small embedding model (about 25 MB, downloaded once):
it searches every passage of every episode, with the word's own moments shown first
when they fit. The model runs in a Web Worker so the page stays responsive.
In the repository settings, under Pages, set the source to
Deploy from a branch, branch main, folder /docs.
- Auto-generated captions occasionally mis-hear a word, so a handful of mentions may be missing or spurious.
- Episode selection is heuristic: the show's playlist, an episode number in the title, or the show's name in the title, longer than 30 minutes.
- Timestamps come from the caption cue, so a link may land a second or two before the word is spoken.