ozymandias/README.md

120 lines
6.8 KiB
Markdown

# Ozymandias
A full-featured static site framework built with [Hakyll](https://jaspervdj.be/hakyll/) and [Pandoc](https://pandoc.org/). Designed for long-form writing, research, music, and creative work.
## What's included
- **Sidenotes** — footnotes render in the margin on wide screens, inline on mobile.
- **Epistemic profiles** — tag essays with confidence, evidence quality, importance, and stability; readers see a compact credibility signal before committing to read.
- **Backlinks** — two-pass wikilink resolution with automatic backlink sections.
- **Score reader** — swipeable SVG score viewer for music compositions.
- **Photography** — opt-in section with masonry / grid / chronological / map / contact-sheet views, EXIF + palette auto-extraction, geo-precision-aware Leaflet map, and a darkroom-mode lightbox. Activates automatically when `content/photography/` exists.
- **Typography** — dropcaps, smallcaps auto-detection, abbreviation tooltips, old-style figures.
- **Math** — KaTeX rendering for inline and display equations.
- **Citations** — Pandoc citeproc with Chicago Notes; bibliography and further-reading sections.
- **Search** — Pagefind client-side full-text search.
- **Semantic search** — optional embedding pipeline (sentence-transformers + FAISS) for "similar links."
- **Settings** — dark mode, text size, focus mode, reduce motion.
- **Wikilinks** — `[[Page Name]]` and `[[Page Name|display text]]` syntax.
- **PDF embeds** — drop a PDF into `static/papers/`, then `{{pdf:/papers/foo.pdf}}` (or `#5` for a starting page) renders it inline via a vendored PDF.js viewer; `[Foo](/papers/foo.pdf)` links auto-rewrite to the same viewer. First-page thumbnails are auto-generated when `pdftoppm` (poppler) is available.
- **Atom feeds** — site-wide and per-section (e.g., music-only, photography-only).
- **Library** — configurable portal taxonomy that groups content by tag hierarchy.
- **Version history** — git-derived stability heuristic with manual history annotations.
- **Reading mode** — dedicated layout for poetry and fiction.
- **GPG signing** — optional per-page detached signatures.
## Quickstart
```sh
# Clone and enter the repo
git clone <your-fork-url> my-site && cd my-site
# Edit your identity and navigation
$EDITOR site.yaml
# Build and serve locally (requires GHC 9.6+, cabal, pagefind)
make dev
```
### Build commands
| Command | What it does |
|:--------------|:---------------------------------------------------------------------------------------------------------------|
| `make dev` | Fast iteration build with drafts visible (`SITE_ENV=dev`), then `python3 -m http.server :8000`. Skips pagefind, image conversion, EXIF/palette/dimension extraction, embeddings, and signing — search and similar-links are inactive. |
| `make watch` | Same draft-visible mode as `dev`, but uses Hakyll's `watch` server with auto-rebuild on source changes. Same skip list as `dev`. |
| `make build` | Full production build into `_site/`: WebP conversion, PDF thumbnails, photography sidecars (when `content/photography/` exists), Hakyll, pagefind, embeddings. |
| `make sign` | Detach-sign every `.html` in `_site/` with the GPG key configured in `site.yaml`'s `gpg-fingerprint`. Requires `tools/preset-signing-passphrase.sh` to have cached the passphrase. |
| `make deploy` | `clean` + `build` + `sign` + rsync to `$VPS_USER@$VPS_HOST:$VPS_PATH/` + `git push`. VPS vars come from `.env` (gitignored). |
| `make clean` | Hakyll clean (removes `_site/` and `_cache/`). |
## Prerequisites
- **GHC 9.6+** and **cabal-install** — for the Haskell build pipeline.
- **Pagefind** — client-side search index (`npm i -g pagefind` or via your package manager).
- **cwebp** (optional) — for automatic WebP image conversion (`pacman -S libwebp` / `apt install webp`).
- **Python 3.12+ and uv** (optional) — for the embedding pipeline (`uv sync` to set up).
## Configuration
All site identity, navigation, and taxonomy live in `site.yaml`:
```yaml
site-name: "My Site"
site-url: "https://example.com"
author-name: "Your Name"
nav:
- { href: "/", label: "Home" }
- { href: "/library.html", label: "Library" }
portals:
- { slug: "writing", name: "Writing" }
- { slug: "code", name: "Code" }
```
See the comments in `site.yaml` for the full schema.
## Project structure
```
build/ Haskell source — Hakyll rules, Pandoc filters, compilers
templates/ Hakyll HTML templates and partials
static/ CSS, JS, fonts, images (copied to _site/)
content/ Markdown source — essays, blog, poetry, fiction, music
data/ Bibliography, annotations, citation style
tools/ Shell/Python build-time utilities
site.yaml Site-wide configuration
Makefile Build, deploy, dev targets
```
## Content types
| Type | Path | Template |
|:------------|:-------------------------------------------|:------------------|
| Essay | `content/essays/*.md` | essay.html |
| Blog post | `content/blog/*.md` | blog-post.html |
| Poetry | `content/poetry/*.md` | reading.html |
| Fiction | `content/fiction/*.md` | reading.html |
| Composition | `content/music/<slug>/index.md` | composition.html |
| Photo | `content/photography/<slug>/index.md` | photography.html |
| Page | `content/*.md` | page.html |
The photography section is opt-in: leave `content/photography/` absent and zero photo rules run, no leaflet is downloaded, no sidecars are generated. To turn it on, create the directory with an `index.md` (the section landing) and a first photo entry. Add `{ slug: "photography", name: "Photography" }` to `site.yaml`'s `portals` to surface a library shelf. Each photo entry can specify a JPEG file, EXIF and palette sidecars are auto-generated at build time (Pillow + colorthief), and a geo-precision-rounded `map.json` feeds the Leaflet view at `/photography/map/`.
## Deployment
The included `make deploy` target:
1. Runs `make clean && make build && make sign`
2. Rsyncs `_site/` to a VPS (configured via `.env`)
3. Pushes to the git remote
Set `VPS_USER`, `VPS_HOST`, and `VPS_PATH` in `.env` (gitignored).
## License
The framework code (everything outside `content/`) is [MIT](LICENSE). The demo content under `content/` is public domain. Your own content is yours — add whatever license you choose.
---
*"My name is Ozymandias, King of Kings; / Look on my Works, ye Mighty, and despair!"* — the name is a reminder that all frameworks are temporary, but the writing you put in them might not be.