118 lines
5.4 KiB
Markdown
118 lines
5.4 KiB
Markdown
# 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
|