# Writing Guide

Reference for creating content on levineuwirth.org. Covers file placement, all
frontmatter fields, and every authoring feature available in the Markdown source.

---

## File placement

| Type | Location | Output URL |
|------|----------|------------|
| Essay | `content/essays/my-essay.md` | `/essays/my-essay.html` |
| Blog post | `content/blog/my-post.md` | `/blog/my-post.html` |
| Poetry | `content/poetry/my-poem.md` | `/poetry/my-poem.html` |
| Fiction | `content/fiction/my-story.md` | `/fiction/my-story.html` |
| Composition | `content/music/{slug}/index.md` | `/music/{slug}/` |
| Standalone page | `content/my-page.md` | `/my-page.html` |
| Standalone page (with co-located assets) | `content/my-page/index.md` | `/my-page.html` |

File names become URL slugs. Use lowercase, hyphen-separated words.

If a standalone page embeds co-located SVG score fragments or other relative assets,
place it in its own directory (`content/my-page/index.md`) rather than as a flat file.
Score fragment paths are resolved relative to the source file's directory; a flat
`content/my-page.md` would resolve them from `content/`, which is wrong.

---

## Frontmatter

Every file begins with a YAML block fenced by `---`. Keys are read by Hakyll
and passed to templates; unknown keys are silently ignored and can be used as
custom template variables.

### Essays

```yaml
---
title: "The Title of the Essay"
date: 2026-03-15          # required; used for ordering, feed, and display
abstract: >               # optional; shown in the metadata block and link previews
  A one-paragraph description of the piece.
tags:                     # optional; see Tags section
  - nonfiction
  - nonfiction/philosophy
authors:                  # optional; overrides the default "Levi Neuwirth" link
  - "Levi Neuwirth | /me.html"
  - "Collaborator | https://their.site"
  - "Plain Name"          # URL optional; omit for plain-text credit
further-reading:          # optional; see Citations section
  - someKey
  - anotherKey
bibliography: data/custom.bib   # optional; overrides data/bibliography.bib
csl: data/custom.csl            # optional; overrides Chicago Author-Date
no-collapse: true               # optional; disables collapsible h2/h3 sections
js: scripts/my-widget.js        # optional; per-page JS file (see Page scripts)
# js: [scripts/a.js, scripts/b.js]  # or a list

# Epistemic Effort — all optional; the entire section is hidden unless `status` is set
status: "Working model"   # Draft | Working model | Durable | Refined | Superseded | Deprecated
confidence: 72            # 0–100 integer (%)
importance: 3             # 1–5 integer (rendered as filled/empty dots ●●●○○)
evidence: 2               # 1–5 integer (same)
scope: average            # personal | local | average | broad | civilizational
novelty: moderate         # conventional | moderate | idiosyncratic | innovative
practicality: moderate    # abstract | low | moderate | high | exceptional
confidence-history:       # list of integers; trend arrow derived from last two entries
  - 55
  - 63
  - 72

# Version history — optional; falls back to git log, then to date frontmatter
history:
  - date: "2026-03-01"    # ISO date as a quoted string (prevent YAML date parsing)
    note: Initial draft
  - date: "2026-03-14"
    note: Expanded typography section; added citations
---
```

### Blog posts

Same fields as essays. `date` formats the `<time>` element in the post header
and blog index. Posts appear in `/feed.xml`.

### Poetry

Same fields as essays. Poetry uses a narrow-measure codex reading mode
(`reading.html`): 52ch measure, 1.85 line-height, stanza spacing, no drop cap.
`poetryCompiler` enables `Ext_hard_line_breaks` — each source newline becomes
a `<br>`, so verse lines render without trailing-space tricks.

**External / non-original poems** — use `poet:` instead of `authors:` to credit
an external author without generating a (broken) author index page:

```yaml
---
title: Sonnet 60
date: 1609-05-20
poet: William Shakespeare
tags: [poetry]
---
```

`poet:` is a plain string. The metadata block shows "by William Shakespeare" as
plain text. Do not use `authors:` for poems you did not write; that would create
an `/authors/william-shakespeare/` index page linking to the poem.

### Fiction

Same fields as essays. Fiction uses the same codex reading mode as poetry:
62ch measure, chapter drop caps + smallcaps lead-in on `h2 + p`.

### Standalone pages

Only `title` is required. Pages use `pageCtx`: no TOC, no bibliography, no
reading-time, no epistemic footer.

```yaml
---
title: About
---
```

Add `js:` for any page-specific interactivity (see Page scripts).

### Music compositions

Compositions live in their own directory. See the Music section for full details.

```yaml
---
title: "Symphony No. 1"
date: 2026-01-15
abstract: >
  A four-movement work for large orchestra.
tags: [music]
instrumentation: "orchestra (2222/4231/timp+2perc/str)"
duration: "ca. 32'"
premiere: "2026-05-20"
commissioned-by: "—"              # optional
recording: audio/full-recording.mp3  # optional; full-piece audio player
pdf: scores/symphony.pdf          # optional; download link
category: orchestral              # orchestral | chamber | solo | vocal | choral | electronic
featured: true                    # optional; appears in Featured section of /music/
score-pages:                      # required for score reader; omit if no score
  - scores/page-01.svg
  - scores/page-02.svg
movements:                        # optional
  - name: "I. Allegro"
    page: 1                       # 1-indexed starting page in the reader
    duration: "10'"
    audio: audio/movement-1.mp3   # optional; per-movement player
  - name: "II. Adagio"
    page: 18
    duration: "12'"
---
```

---

## Tags

Tags use slash-separated hierarchy. `nonfiction/philosophy` expands to both
`nonfiction` and `nonfiction/philosophy`, so the piece appears on both index
pages.

```yaml
tags:
  - nonfiction/philosophy
  - research/epistemology
```

The top-level segment maps to a **portal** in the nav:

| Portal | URL |
|--------|-----|
| AI | `/ai/` |
| Fiction | `/fiction/` |
| Miscellany | `/miscellany/` |
| Music | `/music/` |
| Nonfiction | `/nonfiction/` |
| Poetry | `/poetry/` |
| Research | `/research/` |
| Tech | `/tech/` |

Tag index pages are paginated at 20 items and auto-generated on build.

---

## Authors

The `authors` key controls the author line in the metadata block. When omitted,
it defaults to "Levi Neuwirth" linking to `/authors/levi-neuwirth/`.

Author pages are generated at `/authors/{slug}/` and list all attributed content.

Pipe syntax: `"Name | URL"` — name is the link text, URL is the `href`.
The URL part is optional.

---

## Citations

The citation pipeline uses Chicago Author-Date style. The bibliography lives at
`data/bibliography.bib` (BibLaTeX format) by default; override per-page with
`bibliography` and `csl`.

### Inline citations

```markdown
This claim is contested.[@smith2020]

Multiple sources agree.[@jones2019; @brown2021]
```

Inline citations render as numbered superscripts `[1]`, `[2]`, etc. The
bibliography section appears automatically in the page footer. `citations.js`
adds hover previews showing the full reference.

### Further reading

Keys under `further-reading` appear in a separate **Further Reading** section
in the page footer. These are not cited inline.

```yaml
further-reading:
  - keyNotCitedInline
  - anotherBackgroundSource
```

A key can appear both inline and in `further-reading`; it is numbered in the
bibliography and not duplicated.

---

## Footnotes → sidenotes

Standard Markdown footnotes are converted to sidenotes at build time.

```markdown
This sentence has a note.[^1]

[^1]: The note content, which may contain **bold**, *italic*, links, etc.
```

On wide viewports the note floats into the right margin. On narrow viewports
it falls back to a standard footnote. Sidenotes are numbered automatically.

Avoid block-level elements (headings, lists) inside footnotes.

---

## Wikilinks

Internal links can be written as wikilinks. The title is slugified to produce
the URL.

```markdown
[[Page Title]]             → links to /page-title
[[Page Title|Link text]]   → links to /page-title with custom display text
```

Slug rules: lowercase, spaces → hyphens, non-alphanumeric characters removed.
`[[My Essay (2024)]]` → `/my-essay-2024`.

Use standard Markdown link syntax `[text](/path)` for any link whose path is
not derivable from the page title.

---

## Math

KaTeX renders client-side from raw LaTeX. CSS and JS are loaded conditionally
on pages that have `math: true` set in their context (all essays and posts have
this by default).

| Syntax | Usage |
|--------|-------|
| `$...$` | Inline math |
| `$$...$$` on its own line | Display math |

```markdown
The quadratic formula is $x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$.

$$
\int_0^\infty e^{-x^2}\,dx = \frac{\sqrt{\pi}}{2}
$$
```

---

## Links

Internal links: standard Markdown or wikilinks.

External links are classified automatically at build time:

- Opened in a new tab (`target="_blank" rel="noopener noreferrer"`)
- Decorated with a domain icon via CSS `mask-image`

| Domain | Icon |
|--------|------|
| `wikipedia.org` | Wikipedia |
| `arxiv.org` | arXiv |
| `doi.org` | DOI |
| `github.com` | GitHub |
| Everything else | Generic external arrow |

### Link preview popups

Hovering over an internal link shows a popup: title, abstract, authors, tags,
reading time. Hovering over a Wikipedia link shows an article excerpt.
Citation markers (`[1]`) show the full reference. All automatic.

---

## Code

Fenced code blocks with a language identifier get syntax highlighting (JetBrains
Mono). The language annotation also enables the selection popup's docs-search
feature (maps to MDN, Hoogle, docs.python.org, etc.).

````markdown
```haskell
fib :: Int -> Int
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)
```
````

Inline code: `` `like this` ``

---

## Images

Place images in `static/images/`. All images automatically get `loading="lazy"`.

```markdown
![Alt text](/images/my-image.png)
```

For a captioned figure, add a title string. Pandoc wraps it in `<figure>`/`<figcaption>`:

```markdown
![Alt text](/images/my-image.png "Caption shown below.")
```

**Lightbox:** standalone images automatically get `data-lightbox="true"`.
Clicking opens a fullscreen overlay; close with ×, click outside, or Escape.
Images inside hyperlinks get lazy loading only — no lightbox.

---

## Section collapsing

By default, every `h2` and `h3` heading in an essay gets a collapse toggle.
State persists in `localStorage`.

To disable on a specific page:

```yaml
no-collapse: true
```

---

## Exhibits and annotations

Pandoc fenced divs (`:::`) create structured callout blocks.

### Exhibits

Always-visible numbered blocks with overlay zoom on click. Use for equations or
proofs you want to reference structurally. Listed under their parent heading
in the TOC.

```markdown
::: {.exhibit .exhibit--equation}
$$E = mc^2$$
:::

::: {.exhibit .exhibit--proof}
*Proof.* Suppose for contradiction that …  ∎
:::
```

### Annotation callouts

Editorial sidebars — context, caveats, tangential detail.

```markdown
::: {.annotation .annotation--static}
Always visible. Use for important caveats or context.
:::

::: {.annotation .annotation--collapsible}
Collapsed by default. Use for tangential detail.
:::
```

---

## Score fragments

Inline SVG music notation, integrated with the gallery/exhibit system. Clicking
a fragment opens the shared overlay alongside any math exhibits on the page.

```markdown
::: {.score-fragment score-name="Main Theme, mm. 1–8" score-caption="The opening gesture."}
![](scores/main-theme.svg)
:::
```

Both attributes are optional, but `score-name` is strongly recommended — it
drives the overlay label and the TOC badge.

The image path is resolved **relative to the source file's directory**:

| Source file | SVG path | Reference as |
|---|---|---|
| `content/essays/my-essay.md` | `content/essays/scores/theme.svg` | `scores/theme.svg` |
| `content/music/symphony/index.md` | `content/music/symphony/scores/motif.svg` | `scores/motif.svg` |
| `content/me/index.md` | `content/me/scores/vln.svg` | `scores/vln.svg` |

SVGs are inlined at build time. Black `fill`/`stroke` values are replaced with
`currentColor` so notation renders correctly in dark mode.

---

## Excerpts

Pull-quotes from other works, displayed as indented blockquotes with a small
attribution line and a stretched invisible link covering the whole figure.

### Poem excerpt

Use when quoting verse that has a dedicated page on the site. The entire figure
becomes a click target pointing to the poem; the attribution line remains an
independent link.

```html
<figure class="poem-excerpt">
<blockquote>

Like as the waves make towards the pebbled shore,
So do our minutes hasten to their end;

</blockquote>
<figcaption><a href="/poetry/sonnet-60.html">William Shakespeare — <em>Sonnet 60</em></a></figcaption>
</figure>
```

The blockquote renders with the default dashed left border and italic muted text.
No box or background. The `figcaption` is small-caps and left-aligned.

### Prose excerpt

For quoting prose or for excerpts that do not have a page on the site:

```html
<figure class="prose-excerpt">
<blockquote>

The passage you want to quote goes here.

</blockquote>
<figcaption><a href="https://example.com">Author — <em>Source Title</em></a></figcaption>
</figure>
```

`prose-excerpt` is full-width (`width: auto; max-width: 100%`) rather than
`fit-content`-wide like `poem-excerpt`. Both reset the image-figure box styles
(no background, no border, no padding).

### Commonplace book

The `/commonplace` page is a YAML-driven quotation collection. Add entries to
`data/commonplace.yaml`; the page rebuilds automatically.

```yaml
- text: |-
    Like as the waves make towards the pebbled shore,
    So do our minutes hasten to their end;
    Each changing place with that which goes before,
    In sequent toil all forwards do contend.
  attribution: William Shakespeare
  source: Sonnet 60
  source-url: /poetry/sonnet-60.html
  tags: [time, mortality]
  commentary: >
    Optional note. Shown below the quote in muted italic. Omit entirely if
    not needed — most entries will not have one.
  date-added: 2026-03-17
```

| Field | Required | Notes |
|-------|----------|-------|
| `text` | yes | Use `|-` block scalar; newlines become `<br>` |
| `attribution` | yes | Author name, plain text |
| `source` | no | Title of the source work |
| `source-url` | no | Makes `source` a link |
| `tags` | no | Separate from content tags; used for "by theme" grouping |
| `commentary` | no | Your own remark on the passage |
| `date-added` | no | ISO date; used for chronological sort |

The page has a **by theme / chronological** toggle (state persists in
`localStorage`). Untagged entries appear under "miscellany" in the themed view.

---

## Music

### Catalog index (`/music/`)

`content/music/index.md` is the catalog homepage. Write prose about your
compositional work in the body; provide an `abstract` for the intro line.
The composition listing is auto-generated from all `content/music/*/index.md`
files — no manual list needed.

```yaml
---
title: Music
abstract: Compositions spanning orchestral, chamber, and solo writing.
tags: [music]
---

[Your prose here — influences, preoccupations, approach to the craft.]
```

### Composition pages

Each composition lives in its own subdirectory:

```
content/music/symphony-no-1/
├── index.md            ← frontmatter + program notes prose
├── scores/
│   ├── page-01.svg     ← one file per score page
│   └── symphony.pdf    ← optional PDF download
└── audio/
    ├── full.mp3        ← optional full-piece recording
    └── movement-1.mp3  ← optional per-movement recordings
```

Two URLs are generated automatically from one source:
- `/music/symphony-no-1/` — prose landing page with metadata, audio players, movements
- `/music/symphony-no-1/score/` — minimal page-turn score reader

The score reader is only generated when `score-pages` is non-empty.

**Catalog indicators** — the `/music/` catalog auto-derives:
- ◼ (score available): `score-pages` list is non-empty
- ♫ (recording available): `recording` key is present, or any movement has `audio`

**Catalog grouping** — `category` controls which section the work appears in.
Valid values: `orchestral`, `chamber`, `solo`, `vocal`, `choral`, `electronic`.
Anything else appears under "Other". Omitting `category` defaults to "other".

**Featured works** — set `featured: true` to also appear in the Featured section
at the top of the catalog.

---

## Page scripts

For pages that need custom JavaScript (interactive widgets, visualisations, etc.),
place the JS file alongside the content and reference it via the `js:` frontmatter
key. The file is copied to `_site/` and injected as a deferred `<script>` at the
bottom of `<body>`.

```yaml
js: scripts/memento-mori.js          # single file
```

or a list:

```yaml
js:
  - scripts/widget-a.js
  - scripts/widget-b.js
```

Paths are relative to the content file. A composition at
`content/music/symphony/index.md` with `js: scripts/widget.js` serves the
script at `/music/symphony/scripts/widget.js`.

No changes to the build system are needed — the `content/**/*.js` glob rule
copies all JS files from `content/` to `_site/` automatically.

---

## Epistemic profile

The epistemic footer section appears when `status` is set. All other fields
are optional and are shown or hidden independently.

| Field | Compact display | Expanded (`<details>`) |
|-------|----------------|------------------------|
| `status` | chip (always shown if present) | — |
| `confidence` | `72%` | — |
| `importance` | `●●●○○` | — |
| `evidence` | `●●○○○` | — |
| `stability` | — | auto-computed from git history |
| `scope` | — | if set |
| `novelty` | — | if set |
| `practicality` | — | if set |
| `last-reviewed` | — | most recent commit date |
| `confidence-trend` | — | ↑/↓/→ from last two `confidence-history` entries |

**Stability** is auto-computed from `git log --follow` at every build. The
heuristic: ≤1 commits or age <14 days → *volatile*; ≤5 commits and age <90
days → *revising*; ≤15 commits or age <365 days → *fairly stable*; ≤30
commits or age <730 days → *stable*; otherwise → *established*.

To pin a manual stability label for one build, add the file path to `IGNORE.txt`
(one path per line). The file is cleared automatically by `make build`.

---

## Version history

The version history footer section uses a three-tier fallback:

1. **`history:` frontmatter** — your authored notes, shown exactly as written.
2. **Git log** — if no `history:` key, dates are extracted from `git log --follow`.
   Entries have no message (date only).
3. **`date:` frontmatter** — if git has no commits for the file, falls back to
   the `date` field as a single "Created" entry.

`make build` auto-commits `content/` before running the Hakyll build, so the
git log stays current without manual commits.

Write authored notes when the git log would be too noisy or insufficiently
descriptive:

```yaml
history:
  - date: "2026-03-01"
    note: Initial draft
  - date: "2026-03-14"
    note: Expanded section 3; incorporated feedback from peer review
```

---

## Typography features

Applied automatically at build time; no markup needed.

| Feature | What it does |
|---------|-------------|
| Smart quotes | `"foo"` / `'bar'` → curly quotes |
| Em dashes | `---` → — |
| En dashes | `--` → – |
| Smallcaps | `[TEXT]{.smallcaps}` → `font-variant-caps: small-caps` |
| Drop cap | First letter of the first `<p>` gets a decorative large initial |
| Explicit drop cap | `::: dropcap` fenced div applies drop cap to any paragraph |
| Ligatures | Spectral `liga`, `dlig` OT features active for body text |
| Old-style figures | Spectral `onum` active; use `.lining-nums` class to override |

To mark a phrase as small caps explicitly:

```markdown
The [CPU]{.smallcaps} handles this.
```

### Explicit drop cap

Wrap any paragraph in a `::: dropcap` fenced div to get a drop cap regardless
of its position in the document. The first line is automatically rendered in
small caps.

```markdown
::: dropcap
COMPOSITION [IS]{.smallcaps} PERHAPS MORE THAN ANYTHING ELSE THE PRACTICE OF MY
LIFE. I say these strong words because I feel strongly about this process.
:::
```

The opening word (or words, before a space) should be written in ALL CAPS in
source — they will render as small caps via `::first-line`. The `[IS]{.smallcaps}`
span is not strictly necessary but can force specific words into the smallcaps
run if needed.

A paragraph that immediately follows a `::: dropcap` block will be indented
correctly (`text-indent: 1.5em`), matching the paragraph-after-paragraph rule.

---

## Text selection popup

Selecting any text (≥ 2 characters) shows a context-aware toolbar after 450 ms.

| Context | Buttons |
|---------|---------|
| Prose (multi-word) | BibTeX · Copy · DuckDuckGo · Here · Wikipedia |
| Prose (single word) | BibTeX · Copy · Define · DuckDuckGo · Here · Wikipedia |
| Math | Copy · nLab · OEIS · Wolfram |
| Code (known language) | Copy · \<MDN / Hoogle / Docs…\> |
| Code (unknown) | Copy |

**BibTeX** generates a `@online{...}` BibLaTeX entry with the selected text in
`note={\enquote{...}}` and copies it to the clipboard. **Define** opens English
Wiktionary. **Here** opens the Pagefind search page pre-filled with the selection.

---

## Page footer sections

Essays get a structured footer. Sections with no data are hidden.

| Section | Shown when |
|---------|-----------|
| Version history | Always (falls back through three-tier system) |
| Epistemic | `status` frontmatter key is present |
| Bibliography | At least one inline citation |
| Further Reading | `further-reading` key is present |
| Backlinks | Other pages link to this page |

Backlinks are auto-generated at build time. No markup needed — any internal
link from another page creates an entry here, showing the source title and the
surrounding paragraph as context.

---

## Build

```bash
make build    # auto-commit content/, compile, run pagefind, clear IGNORE.txt
make deploy   # build + optional GitHub push + rsync to VPS
make watch    # Hakyll live-reload dev server at http://localhost:8000
make dev      # clean build + python HTTP server at http://localhost:8000
make clean    # wipe _site/ and _cache/
```

`make watch` hot-reloads changes to Markdown, CSS, JS, and templates.
**After any change to a `.hs` file, always run `make clean && make build`** —
Hakyll's cache is keyed to source file mtimes and will serve stale output after
Haskell-side changes.

`make deploy` pushes to GitHub if `GITHUB_TOKEN` and `GITHUB_REPO` are set in
`.env` (see `.env.example`), then rsyncs `_site/` to the VPS.
