levineuwirth.org/CLAUDE.md

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 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)
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