Original Site Launch
First version with Tachyons CSS, audio player for DJ mixes, Pinboard bookmarks integration, Are.na vibes section, and a shop. The whole thing was held together with good intentions and functional CSS. It worked, somehow.
This site is a living thing. It grows, breaks, gets rebuilt, and occasionally surprises me.
First version with Tachyons CSS, audio player for DJ mixes, Pinboard bookmarks integration, Are.na vibes section, and a shop. The whole thing was held together with good intentions and functional CSS. It worked, somehow.
Rebuilt the content system with markdown-it parser, table of contents generation, and JSON resume integration. This was the first time the site felt like a real publishing system instead of a bunch of HTML files.
Implemented dark mode toggle and automated photoblog system. 2020 was the year everyone added dark mode. I also automated photo uploads because life's too short to manually resize images.
Added dedicated art pages and updated projects with NBC News and CCRB journalism pieces. The site started pulling double duty as portfolio and blog around this point.
Ripped out Tachyons and replaced it with Tailwind CSS + Nuxt UI. Added 3D canvas experiments and GitHub data integration. The Tachyons-to-Tailwind migration was one of those things that took a weekend but paid off for years.
Personal metrics aggregating GitHub, YouTube, LastFM, Chess.com, and a bunch of other APIs into one dense dashboard. Two versions: full stats for the web (~2.4KB) and a lite version optimized for iOS Shortcuts (~345 bytes). Because sometimes you want to know your chess rating from your watch.
Complete rebuild from scratch. Nuxt 3, Docker deployment, new markdown processing pipeline. Fresh start after 6 years on the old stack. The content pipeline goes Obsidian -> processMarkdown.mjs -> structured JSON -> Vue components. Write in your editor, publish with a git push. The way it should be.
Added a pottery page with Stripe checkout because apparently I make ceramics now. Also RSS feed and a public tags.json endpoint. The pottery page was mostly an excuse to learn Stripe's API, but people actually bought things.
Interactive graph visualization of scraps with Supabase backend integration. The precursor to the threads visualization -- same impulse to see how ideas connect, just rougher around the edges.
Built a full spaced repetition flashcard system because I kept forgetting things I'd already written about. 3D card flip animations, multi-deck support, the whole deal. There's something deeply satisfying about turning your own blog posts into study material. API endpoints for learning data so I can track what sticks.
D3 force-directed graph showing how posts, scraps, and tags actually connect to each other. You scroll through 350vh of interconnected content and watch the relationships emerge. Top 25 tags pinned as maypoles down the page in a sine wave. It's the kind of thing that makes you realize how much you've written about the same three topics.
New page for browsing the Supabase scraps collection. Tag filtering, metadata display, the whole archival experience. Scraps are the stuff that doesn't quite make it into a blog post but is too good to lose -- bookmarks, notes, fragments of ideas. Now they have a proper home.
Live prediction market portfolio tracking with real-time P&L, calibration analysis, and position commentary. I wanted to put my money where my forecasts are, literally. Tracks how well-calibrated my bets are against my stated confidence levels. The calibration curve is humbling.
Rebuilt the gear page with smart unit conversion, favorites, weight-per-category stats, and Tuftian data visualizations. Every piece of gear I own, weighed and categorized. The histogram of weight distribution by category is chef's kiss. 8px baseline grid, ultra-dense tables, sparklines inline. Edward Tufte would approve (I hope).
Major typography overhaul. Footnotes now appear as margin notes on desktop, the way Tufte intended. Replaced an 800-line complex sidenotes system with a 113-line client-side plugin that just works. Delete-driven development at its finest. Georgia serif, proper vertical rhythm, the whole editorial treatment.
Tags endpoint with journalist pyramid ordering -- most important and frequently used tags surface first, like a newsroom would prioritize stories. Also added llms.txt for AI crawlers because the robots should know what they're reading too.
10 predictions for 2025-2029, SHA-256 hashed and committed to git. The idea is simple: write down what you think will happen, commit to it in public, then come back later and see how wrong you were. Accountability through math.
Data-dense scrapbook view with tag filtering and an enhanced Pinboard bookmarklet with smart tag suggestions. The bookmarklet auto-suggests tags based on the page content, which is the kind of small automation that saves you 3 seconds a hundred times a day.
Built the foundation for the forecasting section. Predictions system with cryptographic verification -- SHA-256 hashes locked into git commits so you can't retroactively claim you predicted something you didn't. Honest futures.
Added AI-generated descriptions to 881 images because every reader deserves to know what they're looking at. Used Claude's vision API with a content-first prompt, confidence scoring, and a manual review queue for the tricky ones. Also restored 87 dead Cloudinary images from backups and replaced 10 dead links with Internet Archive URLs. Accessibility isn't a feature, it's the floor.
ci(webmentions): hourly run so scheduled posts get announced
EJ Fox · 1:29 AM
style: prettier on files flagged by lint
EJ Fox · 1:10 AM
deploy: snapshot live content before checkout; every failure path alerts
EJ Fox · 1:07 AM
fix: sealed keys can't be silently replaced; unshared scraps expire; catalog
EJ Fox · 1:06 AM
fix: renderer-hashed cache key, named stats caches, pg pool error handlers
EJ Fox · 12:08 AM
visibility: lint dispatch pieces for a removed hidden: key; soften gone-urls wording
EJ Fox · 11:01 PM
visibility: one function, five states (sealed > draft > scheduled > unlisted > public)
EJ Fox · 10:57 PM
processed: refresh cache that was stale on main (no code change)
EJ Fox · 10:41 PM
plugins: run DOM enhancers on page:finish, not router.afterEach
EJ Fox · 9:26 PM
scraps: chromeless /scraps/embed/<id> for Scrapworld's live card backs
EJ Fox · 3:04 PM
scraps permalink: site typography (Vault Alarm heading, Georgia reading text, mono data)
EJ Fox · 2:03 PM
scraps: per-scrap permalink page /scraps/<id> (shared only)
EJ Fox · 2:02 PM
content(projects): remove made-up and broken project images
EJ Fox · 12:32 AM
fix(scripts): orphan sweep runs, failed posts fail the build, safer author tools
EJ Fox · 6:43 PM
fix(pages,components): reading highlights, gear data, stats tiles, atlas load race, tz
EJ Fox · 6:40 PM
fix(server): harden API input handling, filters and error bodies
EJ Fox · 6:38 PM
fix(api): delete /api/umami/auth
EJ Fox · 5:08 PM
docs(code-guide): allow path-form @file for repeated filenames
EJ Fox · 4:49 AM
docs: fix three comment paths that gf couldn't follow
EJ Fox · 4:49 AM
docs: audit findings from the comment pass; correct two CLAUDE.md claims
EJ Fox · 4:45 AM
docs(server/api): headers for all 94 routes; server/README matches the routes on disk
EJ Fox · 4:44 AM
docs(server utils/middleware/routes): headers, run order, why-comments; server/utils/README
EJ Fox · 4:38 AM
docs(utils,composables,plugins,layouts,types,config): headers, sections, READMEs
EJ Fox · 4:37 AM
docs(pages/*/): headers, sections and why-comments for nested pages
EJ Fox · 4:36 AM
docs(scripts/build,plugins,utils): pipeline map in processMarkdown, headers and why-comments
EJ Fox · 4:35 AM
docs(components): headers, sections and why-comments for all 72 components; components/README
EJ Fox · 4:32 AM
docs(pages): headers, sections and why-comments for top-level pages; pages/README route map
EJ Fox · 4:30 AM
docs(scripts): headers + why-comments for meta/author/blog/qa/og-image; fix stale README
EJ Fox · 4:28 AM
docs: repo map, code guide, docs index; fix stale README claims
EJ Fox · 4:23 AM
fix(light mode): readable secondary text and status colors
EJ Fox · 8:10 PM
fix(layout): stop posts reflowing and overflowing on phones
EJ Fox · 7:36 PM
chore: delete ~1,000 dead lines; tag 3 code fences; add .ignore
EJ Fox · 7:07 PM
refactor(crown): a big post is a .vue body next to its .md
EJ Fox · 5:38 PM
chore: remove rotated images everywhere
EJ Fox · 5:33 PM
fix(crown): images fade in at their tilt; no rotation on scroll
EJ Fox · 5:14 PM
fix(crown): images ease into their tilt instead of snapping
EJ Fox · 4:57 PM
fix(crown): useCrownedPost 500'd on SSR; light-mode meta bar
EJ Fox · 4:49 PM
fix(crown): format with prettier's API, not npx
EJ Fox · 4:38 PM
fix(crown): clean feed HTML; sealed check first
EJ Fox · 4:37 PM
fix(crown): bind srcset so Nuxt doesn't import Cloudinary URL pieces
EJ Fox · 4:34 PM
feat(crown): a crowned post's prose lives in its Vue page
EJ Fox · 4:25 PM
ci: smoke-test ejfox.com after every deploy, alert Discord on failure
EJ Fox · 4:11 PM
test: yarn smoke, a read-only production smoke test
EJ Fox · 3:55 PM
content: delete 2022-prototyping-toolkit; hook skips without deps
EJ Fox · 6:14 PM
ci: fail when a public URL stops resolving (check-urls)
EJ Fox · 2:52 PM
content: re-publish how-i-built-coach-artie
EJ Fox · 2:33 PM
a11y: dark-mode zinc-500 one notch lighter (contrast 4.35 → 4.6:1)
EJ Fox · 5:42 PM
/stats goes live: background refresh with a visible "updated" flash
EJ Fox · 2:41 AM
/stats: hydrate sections on scroll, skip offscreen layout, defer 700KB fetch
EJ Fox · 2:03 AM
/calendar quick slots: full-width rows on phones instead of a cramped 3-up
EJ Fox · 5:02 PM