# 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
