levineuwirth.org/CLAUDE.md

118 lines
5.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 650700px (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