diff --git a/docs/reference-pages.md b/docs/reference-pages.md
new file mode 100644
index 0000000..f1e4607
--- /dev/null
+++ b/docs/reference-pages.md
@@ -0,0 +1,237 @@
+# Page reference — frontmatter and mdcms tags
+
+All keys you can use inside a markdown page in `pages/` or `posts/`.
+
+A page has two parts:
+
+```markdown
+---
+# Frontmatter (YAML, optional except for title)
+title: My Page
+---
+
+Markdown body goes here.
+
+```mdcms
+toc
+```
+
+Regular markdown, plus mdcms code blocks for callouts, table of contents, post lists.
+```
+
+---
+
+## Frontmatter
+
+The YAML block delimited by `---` at the top of the file. Read by `mdcms build` to populate `nav.yml` and `search.json`, and by `index.html` at runtime to set the page title, dates, and meta tags.
+
+```yaml
+---
+title: Page Title # REQUIRED. Browser tab title, nav label, h1 fallback.
+ # Without this, the page is skipped from nav.yml.
+
+sort: 100 # Position in the nav within its section. Lower = higher.
+ # Default: 100. Tiebreaker is filename.
+
+section-id: guides # Assigns this page to a section. Must match (or auto-create)
+ # a code: in nav.yml. Omit to leave unsectioned.
+
+draft: true # Excludes the page from nav.yml AND search.json.
+ # Default: false.
+
+author: Jane Doe # Shown in the meta line under the page title (pages with author or created).
+
+created: 2026-05-18 14:30 # Publish date. Format: YYYY-MM-DD or YYYY-MM-DD HH:MM.
+ # Required for posts to appear in posts-* tag listings.
+ # Used as the sort key in chronological/reverse-chronological lists.
+
+modified: 2026-05-19 09:15 # Last-modified date. Shown next to created date if set.
+
+description: Short summary # Used for the tag.
+ # Falls back to config.yml sitedescription if omitted.
+
+keywords: foo, bar, baz # Comma-separated. Indexed in search.json.
+
+language: en # BCP 47 code. Sets the attribute when this page is loaded.
+ # Doesn't filter pages — that's what categories are for.
+---
+```
+
+**Category variants** are not a frontmatter field — they are encoded in the filename. `about.nb.md` is the Norwegian variant of `about.md`, provided `nb` is declared in `config.yml` under `categories:`.
+
+---
+
+## mdcms code blocks
+
+Fenced blocks with the `mdcms` language tag are intercepted by the renderer and replaced with dynamic HTML. The tag name goes either on the fence line or on the first line of the block:
+
+````markdown
+```mdcms callout-info
+title: Heads up
+This is the body.
+```
+````
+
+…is equivalent to:
+
+````markdown
+```mdcms
+callout-info
+title: Heads up
+This is the body.
+```
+````
+
+Inside the block, lines matching `key: value` are parsed as options. The first non-matching line begins the body.
+
+---
+
+### Callout tags — `callout-info`, `callout-warning`, `callout-success`, `callout-error`
+
+A bordered, tinted box for notes, warnings, success messages, errors. Colour and icon come from `theme.yml` (`callouts:` block); fall back to built-in defaults.
+
+````markdown
+```mdcms callout-info
+title: Note # Optional. Bold title row with icon. Omit for a body-only callout.
+icon: lightbulb # Optional. Override the default icon. Use an SVG name from assets/icons/.
+message: aitranslation # Optional. Resolves title + body from config.yml callouts: block.
+ # Takes precedence over inline title/body.
+
+Body text supports **full markdown** — bold, *italics*, `code`,
+[links](https://example.com), lists, etc.
+
+- item one
+- item two
+```
+````
+
+**Behaviour:**
+- Type comes from the tag name suffix (`info`/`warning`/`success`/`error`).
+- `message: ` looks up the named block in `config.yml`. When matched, the message's title and body override any inline values. The message's `type:` also overrides the tag type.
+- For multi-language messages, the renderer picks the entry for the active category, then the default category, then the first key.
+
+---
+
+### Table of contents — `toc`
+
+Renders a section-grouped, sorted list of all visible non-draft pages in the active category. The page containing the tag is excluded.
+
+````markdown
+```mdcms
+toc
+```
+````
+
+No options. Output is grouped by nav section in section sort order; pages within each section follow their own `sort:`.
+
+---
+
+### Post listings — `posts-created-*`
+
+Generate a chronologically sorted list of posts (files in `posts/`). Requires each post to have a `created:` value in frontmatter.
+
+**Reliable variants** (others are broken — do not use):
+
+```
+posts-created-chronological-byyearmonth
+posts-created-reversechronological
+```
+
+The grammar:
+
+```
+posts-created-[-]
+ order: chronological | reversechronological
+ modifier: byyear | byyearmonth | lastyear | lastmonth (optional)
+```
+
+- `byyear` / `byyearmonth` — group output by year, or by year-and-month.
+- `lastyear` / `lastmonth` — filter to posts from the last 365/30 days.
+- No modifier — flat list of all posts.
+
+````markdown
+```mdcms
+posts-created-reversechronological
+limit: 10 # Max number of posts shown. Default: all.
+ # When paginate: yes, this is the page size (batch size).
+
+paginate: yes # Pagination mode:
+ # yes — show a "load more" button after batchSize posts.
+ # none — show only the first posts, no pagination.
+ # no — show all posts at once (default).
+```
+````
+
+**Category filtering:** When `categories-use: yes`, the listing automatically filters to the active category.
+
+---
+
+## Markdown features
+
+Standard CommonMark plus GFM (GitHub-flavoured) extensions:
+
+- Tables
+- Strikethrough (`~~text~~`)
+- Task lists (`- [ ]` / `- [x]`)
+- Fenced code blocks with syntax language hints (`` ```python ``)
+- Autolinks
+
+**Raw HTML** passes through to the DOM. You can embed HTML directly:
+
+```markdown
+
+```
+
+**Scripts injected via `