5.4 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
levineuwirth.org is a personal website built as a static site using Hakyll (Haskell) + Pandoc. The spec is in spec.md. The site is inspired by gwern.net in architecture: sidenotes, semantic zoom, monochrome typography, Pandoc filters, no client-side tracking.
Build Commands
make build # cabal run site -- build && pagefind --site _site
make deploy # build + rsync -avz --delete _site/ vps:/var/www/levineuwirth.com/
make watch # cabal run site -- watch (live-reload dev server)
make clean # cabal run site -- clean
Important: Hakyll caches compiled items in _cache/ keyed to source file mtimes.
Changing a .hs file (filter, compiler, context) does not invalidate the cache for
existing content files. Always run make clean && make build after any Haskell-side change,
or content will be served from the stale cache.
The Haskell build program lives in build/. The entry point is build/Main.hs.
Architecture
Build System (build/)
The Hakyll site compiler is split into focused modules:
Main.hs— entry pointSite.hs— all Hakyll rules (which patterns compile to which outputs)Compilers.hs— custom Pandoc compiler wrappersContexts.hs— Hakyll template contexts (includes auto-computedword-count,reading-time)Metadata.hs— loads YAML frontmatter + merges external JSON fromdata/Tags.hs— hierarchical tag system using HakyllbuildTagsPagination.hs— 20/page for blog and tag indexes; essays all on one pageCitations.hs— citeproc + BibLaTeX + Chicago Author-Date; bib file atdata/bibliography.bibFilters.hs— re-exports all Pandoc AST filter modulesFilters/Typography.hs— smart quotes, dashes, etc.Filters/Sidenotes.hs— converts footnotes to sidenotesFilters/Dropcaps.hs— decorative drop capitalsFilters/Smallcaps.hs— smallcaps viasmcpOT featureFilters/Wikilinks.hs—[[wikilink]]syntaxFilters/Links.hs— external link classification and icon injectionFilters/Math.hs— simple LaTeX → Unicode at build time; complex math → KaTeX SSR (static HTML+MathML)Utils.hs— shared helpers
Math Pipeline
Two-tier, no client-side JS required:
- Simple math → Unicode/HTML via Pandoc Lua filter (inherits body font)
- Complex math → KaTeX server-side rendering → static HTML+MathML (KaTeX CSS loaded conditionally, only on pages that use math)
CSS (static/css/)
| File | Purpose |
|---|---|
base.css |
CSS variables, palette, dark mode ([data-theme="dark"] + prefers-color-scheme) |
typography.css |
Spectral OT features: smallcaps (smcp), ligatures, figure styles, dropcaps |
layout.css |
Three-column layout: sticky TOC (left) |
sidenotes.css |
Sidenote positioning |
popups.css |
Link preview popups |
syntax.css |
Monochrome code highlighting (JetBrains Mono) |
components.css |
Two-row nav, metadata block, collapsibles |
JavaScript (static/js/)
| File | Source | Purpose |
|---|---|---|
sidenotes.js |
Adopted — Said Achmiz (MIT) | Sidenote positioning |
popups.js |
Forked + simplified — Said Achmiz (MIT) | Internal previews, Wikipedia, citation previews |
theme.js |
Original | Dark/light toggle with localStorage |
toc.js |
Original | Sticky TOC + scroll tracking via IntersectionObserver |
search.js |
Original | Pagefind integration |
nav.js |
Original | Portal row expand/collapse (state in localStorage) |
collapse.js |
Original | Section collapsing |
Typography
- Body: Spectral (SIL OFL) — self-hosted WOFF2, subsetted with full OT features (
liga,smcp,onum, etc.) - UI/Headers: Fira Sans (SIL OFL) — smallcaps for primary nav row
- Code: JetBrains Mono (SIL OFL)
All fonts self-hosted from source (not Google Fonts, which strips OT features). Subset with pyftsubset.
Navigation Structure
Home | Me | New | Index | [🔍] ← primary row (always visible), Fira Sans smallcaps
────────────────────────────────
▼ Fiction | Miscellany | Music | Nonfiction | Poetry | Research ← portal row
Portal row collapsed by default; expansion state in localStorage.
Content Portals
Six content portals map to content/ subdirectories: Fiction, Miscellany, Music, Nonfiction, Poetry, Research. Essays live under Nonfiction; blog posts are a separate stream.
Metadata
Frontmatter keys for Phase 1: title, created, modified, status, tags, abstract.
Auto-computed at build: word-count, reading-time.
External data loaded from data/annotations.yaml and data/bibliography.bib.
Deployment
Local build → _site/ → rsync to VPS. No server-side processing; nginx serves static files. No Docker.
Key Design Constraints
- No tracking, no analytics, no fingerprinting — enforced at the nginx CSP header level too
- No client-side math rendering — KaTeX runs at build time
- Sidenotes right-column only (matching gwern's
useLeftColumn: () => false) - Configuration is code — the Makefile and Haskell build system are the deployment pipeline
- Content license: CC BY-SA-NC 4.0 | Code license: MIT