# 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 ```bash 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 point - `Site.hs` — all Hakyll rules (which patterns compile to which outputs) - `Compilers.hs` — custom Pandoc compiler wrappers - `Contexts.hs` — Hakyll template contexts (includes auto-computed `word-count`, `reading-time`) - `Metadata.hs` — loads YAML frontmatter + merges external JSON from `data/` - `Tags.hs` — hierarchical tag system using Hakyll `buildTags` - `Pagination.hs` — 20/page for blog and tag indexes; essays all on one page - `Citations.hs` — citeproc + BibLaTeX + Chicago Author-Date; bib file at `data/bibliography.bib` - `Filters.hs` — re-exports all Pandoc AST filter modules - `Filters/Typography.hs` — smart quotes, dashes, etc. - `Filters/Sidenotes.hs` — converts footnotes to sidenotes - `Filters/Dropcaps.hs` — decorative drop capitals - `Filters/Smallcaps.hs` — smallcaps via `smcp` OT feature - `Filters/Wikilinks.hs` — `[[wikilink]]` syntax - `Filters/Links.hs` — external link classification and icon injection - `Filters/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: 1. Simple math → Unicode/HTML via Pandoc Lua filter (inherits body font) 2. 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) | body 650–700px (center) | sidenotes (right). Collapses on narrow screens. | | `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