diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 70167af..f500fae 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -1,93 +1,77 @@
+# Build the documentation, and publish it from main.
+#
+# ⛔ No Python. What this replaced was MkDocs Material plus mike: a
+# requirements.txt, a pip install and a Python setup step, to render a static
+# site for an organisation whose whole argument is that it needs no runtime but
+# Go. Hugo is one binary, and the theme is a Go module.
name: docs
on:
push:
branches: [main]
+ # On a pull request too, so a change that breaks the build is caught before
+ # it lands rather than by the published site going stale.
pull_request:
workflow_dispatch:
-# The build needs nothing beyond the source. Write access to the repository
-# is granted to the deploy job alone, below, so a pull request build cannot
-# push to gh-pages.
permissions:
contents: read
+ pages: read
+
+concurrency:
+ group: pages-${{ github.ref }}
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
- # Build on every pull request as well as on main, so a change to the docs or to
- # the toolchain in requirements.txt has to prove it still builds before it
- # lands. Without this the workflow ran only on main, which meant a dependency
- # bump had no signal at all and nothing could say whether it was safe to merge.
build:
runs-on: ubuntu-latest
steps:
- - name: Checkout
- uses: actions/checkout@v7
+ - uses: actions/checkout@v7
+ with:
+ # The theme reads the last-modified date of each page from git, so a
+ # shallow clone would date every page to the checkout.
+ fetch-depth: 0
+
+ - uses: actions/setup-go@v7
+ with:
+ # Pinned: Hugo modules are Go modules, and the resolver is the one in
+ # this toolchain.
+ go-version: '1.27.1'
- - name: Set up Python
- uses: actions/setup-python@v7
+ - uses: peaceiris/actions-hugo@v3
with:
- python-version: "3.14"
- cache: pip
- cache-dependency-path: requirements.txt
+ hugo-version: '0.167.0'
+ extended: true
+
+ - uses: actions/configure-pages@v6
+ id: pages
- - name: Install MkDocs Material + mike
- run: |
- pip install --upgrade pip
- pip install -r requirements.txt
+ - name: Build
+ # --minify and the base URL Pages computes, the same way the landing
+ # repository does it.
+ run: hugo --minify --baseURL "${{ steps.pages.outputs.base_url }}/"
- # --strict turns a broken link or an unknown config key into a failure
- # rather than a warning nobody reads.
- - name: Build the site (strict)
- run: mkdocs build --strict
+ - name: Say which theme built it
+ # The theme is a FORK reached through a replace directive. Printing the
+ # resolved module means a build that silently fell back to upstream is
+ # visible in the log rather than in the rendering.
+ run: hugo mod graph | head -5
+
+ - uses: actions/upload-pages-artifact@v5
+ with:
+ path: public
deploy:
- # Only main publishes. A pull request stops after build above: it has no
- # business pushing to gh-pages, and `permissions: contents: write` is not
- # granted to a fork's PR anyway.
- if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
+ if: github.event_name != 'pull_request'
needs: build
- # Two deploys must never run at once: `mike deploy --push` pushes gh-pages,
- # and a second job doing the same is rejected outright --
- #
- # ! [rejected] gh-pages -> gh-pages (fetch first)
- #
- # cancel-in-progress stays FALSE deliberately: killing a publish half way
- # through is worse than making it wait, and the queued run then fetches the
- # gh-pages the previous one just wrote.
- concurrency:
- group: gh-pages-deploy
- cancel-in-progress: false
runs-on: ubuntu-latest
permissions:
- contents: write
+ contents: read
+ pages: write
+ id-token: write
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
steps:
- - name: Checkout
- uses: actions/checkout@v7
- with:
- fetch-depth: 0
-
- - name: Set up Python
- uses: actions/setup-python@v7
- with:
- python-version: "3.14"
-
- - name: Install MkDocs Material + mike
- run: |
- pip install --upgrade pip
- pip install -r requirements.txt
-
- - name: Configure git for mike
- run: |
- git config user.name "github-actions[bot]"
- git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
-
- # mike publishes versioned docs to the gh-pages branch; GitHub Pages serves
- # that branch at https://go-pdfkit.github.io/docs/. The version label
- # tracks main as pdfkit's current minor release ("0.4", matching tag
- # v0.4.0) aliased "latest"; bump the label here on every minor release.
- # /docs/ redirects to the default ("latest").
- - name: Deploy versioned docs
- run: |
- git fetch origin gh-pages --depth=1 || true
- mike deploy --push --update-aliases 0.4 latest
- mike set-default --push latest
+ - id: deployment
+ uses: actions/deploy-pages@v5
diff --git a/.gitignore b/.gitignore
index b44ca6c..d13a188 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
-site/
-.cache/
+# Hugo's output and its caches.
+public/
+resources/
+.hugo_build.lock
diff --git a/README.md b/README.md
index 764ba9e..f76f745 100644
--- a/README.md
+++ b/README.md
@@ -1,23 +1,54 @@
# go-pdfkit docs
-Source for **** — MkDocs Material,
-versioned with [mike](https://github.com/jimporter/mike).
-
-Preview locally:
+Hugo source for ****.
```sh
-pip install -r requirements.txt
-mkdocs serve
+hugo server # preview
+hugo --minify # build into public/
```
-Build:
+## The theme is our fork of Hextra
-```sh
-mkdocs build --strict
+```toml
+# go.mod
+require github.com/imfing/hextra v0.13.0
+replace github.com/imfing/hextra => github.com/tannevaled/hextra v0.16.0
```
-Deployment runs on push to `main` (`.github/workflows/docs.yml`): it
-`mike deploy`s to the `gh-pages` branch, which GitHub Pages serves.
+The fork keeps upstream's module path, so it is reached through a `replace`
+rather than by importing a different path, and it is tagged independently and
+ahead — `v0.16.0` against upstream's `v0.13.0`. **This repository is the first
+in the fleet to consume it**, so the two lines above are the pattern the others
+copy.
+
+The build prints `hugo mod graph`, so a build that silently fell back to
+upstream shows in the log rather than in the rendering.
+
+## What this replaced, and why
+
+MkDocs Material plus `mike`: a `requirements.txt`, a `pip install` and a Python
+setup step in CI, to render a static site for an organisation whose whole
+argument is that it needs no runtime but Go. **There is no Python here.**
+
+The thirteen pages were converted by a small Go tool rather than by hand. It
+checks the nav table against the tree **in both directions before writing
+anything** — a page on disk the nav does not name, or a nav entry with no page,
+is an error — then adds front matter, drops a leading `# Heading` that only
+repeats the title, rewrites internal `.md` links to the sections the pages
+moved into, and turns MkDocs admonitions into Hextra callouts.
+
+⚠ That last one is why the conversion was read rather than trusted: an
+admonition is delimited by **indentation**, so rewriting only its opening line
+leaves the body indented four spaces — and Markdown renders that as a **code
+block**. The first version did exactly that, and the opening line had converted
+perfectly.
+
+## Versioning
+
+Not yet. `mike` published `/latest/` and a directory per version; this
+publishes one current set. The fork carries a versioned-documentation kit added
+on 2026-10-05, and adopting it is its own change: moving the generator and the
+versioning scheme in one step would make a failure impossible to attribute.
## License
diff --git a/content/_index.md b/content/_index.md
new file mode 100644
index 0000000..c28c94f
--- /dev/null
+++ b/content/_index.md
@@ -0,0 +1,24 @@
+---
+title: go-pdfkit
+layout: hextra-home
+---
+
+{{< hextra/hero-badge link="https://github.com/go-pdfkit" >}}
+ Pure Go · CGO=0 · BSD-3-Clause
+{{< /hextra/hero-badge >}}
+
+{{< hextra/hero-headline >}}
+ The whole of PDF in Go,
with no C anywhere
+{{< /hextra/hero-headline >}}
+
+{{< hextra/hero-subtitle >}}
+ Read a file, write one, rearrange it, draw it, read it back as words and
pictures, and edit it with other people — in a browser tab if you like.
+{{< /hextra/hero-subtitle >}}
+
+{{< hextra/hero-button text="Read the documentation" link="docs" >}}
+
+{{< hextra/feature-grid >}}
+ {{< hextra/feature-card title="Measured, not asserted" subtitle="Everything here is checked against 118 863 real PDFs — arXiv's figures, Matplotlib, Mathematica, pdfTeX, Ghostscript and Adobe — rather than against files written to pass a test." >}}
+ {{< hextra/feature-card title="Runs on eight architectures" subtitle="amd64, arm64, riscv64, loong64, ppc64le, s390x, 386 and arm — the whole suite runs on each, big-endian and 32-bit included, and on FreeBSD and OpenBSD too." >}}
+ {{< hextra/feature-card title="In a browser tab" subtitle="Every library builds for js/wasm. The workbench opens a PDF, rearranges it and saves it back without any of it leaving the machine." >}}
+{{< /hextra/feature-grid >}}
diff --git a/docs/index.md b/content/docs/_index.md
similarity index 71%
rename from docs/index.md
rename to content/docs/_index.md
index 5c81e15..5beff7c 100644
--- a/docs/index.md
+++ b/content/docs/_index.md
@@ -1,3 +1,8 @@
+---
+title: "Overview"
+weight: 10
+---
+
# go-pdfkit documentation
**The whole of PDF in Go, with `CGO_ENABLED=0` and no C anywhere**: read a
@@ -10,24 +15,28 @@ Fonts are parsed and shaped with
rasterised by [go-gfx](https://github.com/go-gfx/gfx); nothing outside the Go
standard library and our own pure-Go libraries is required.
-!!! quote "Measured against 118 863 real files"
- Every claim on this site is measured against a corpus of real PDFs —
- arXiv's figures, from Matplotlib and Mathematica and pdfTeX and Ghostscript
- and Adobe — rather than against files written to pass a test. Each wave of
- work is checked by **hashing the pixels of every page before and after** and
- putting the biggest changes beside what the operating system's own renderer
- draws. That is what found a stroke that came out at half its colour, a font
- that took the dots off every *i*, and a colour transform that turned every
- plot's paper yellow.
-
-!!! success "Zero C dependencies"
- No cgo, no bundled `libpoppler`/`libharfbuzz`/`libfreetype`. Glyph
- subsetting, font-descriptor metrics and per-glyph advances all come from
- [go-opentype](https://github.com/go-opentype/opentype)'s
- `Font.SubsetTrueType` / `Font.SubsetCFF` — `pdfkit` keeps no private sfnt
- re-parse or subsetter of its own. See
- [Text & fonts](text-and-fonts.md#font-embedding-architecture).
-
+{{< callout type="info" >}}
+**Measured against 118 863 real files**
+
+Every claim on this site is measured against a corpus of real PDFs —
+arXiv's figures, from Matplotlib and Mathematica and pdfTeX and Ghostscript
+and Adobe — rather than against files written to pass a test. Each wave of
+work is checked by **hashing the pixels of every page before and after** and
+putting the biggest changes beside what the operating system's own renderer
+draws. That is what found a stroke that came out at half its colour, a font
+that took the dots off every *i*, and a colour transform that turned every
+plot's paper yellow.
+{{< /callout >}}
+{{< callout type="info" >}}
+**Zero C dependencies**
+
+No cgo, no bundled `libpoppler`/`libharfbuzz`/`libfreetype`. Glyph
+subsetting, font-descriptor metrics and per-glyph advances all come from
+[go-opentype](https://github.com/go-opentype/opentype)'s
+`Font.SubsetTrueType` / `Font.SubsetCFF` — `pdfkit` keeps no private sfnt
+re-parse or subsetter of its own. See
+[Text & fonts](/docs/writing-a-document/text-and-fonts/#font-embedding-architecture).
+{{< /callout >}}
## The family
| Repo | What it is |
@@ -70,7 +79,7 @@ Ruby-idiomatic writer built on the same font stack.
- **Widget bridge**: `Page.AddWidget` and `Page.AddWidgetVector` "print" a
[go-widgets/toolkit](https://github.com/go-widgets/toolkit) widget tree onto
a page — as a rasterised image, or as PDF vector operators with selectable
- text. See [Widgets](widgets.md).
+ text. See [Widgets](/docs/writing-a-document/widgets/).
- **Deterministic output**: with the zero `Options`, a document has no
timestamps and a content-derived `/ID` (a SHA-256 of the body), so
identical inputs produce byte-identical PDFs.
@@ -84,7 +93,7 @@ Ruby-idiomatic writer built on the same font stack.
- Encryption, tagged PDF / PDF-A, interactive forms and annotations are not
yet implemented.
-See [Scope and limitations](text-and-fonts.md#scope-and-limitations) for the
+See [Scope and limitations](/docs/writing-a-document/text-and-fonts/#scope-and-limitations) for the
full picture, and the upstream
[README](https://github.com/go-pdfkit/pdfkit#scope-and-limitations) for the
canonical statement.
@@ -97,15 +106,15 @@ go get github.com/go-pdfkit/pdfkit
## Where to go next
-- [Getting started](getting-started.md) — the five calls that make a PDF:
+- [Getting started](/docs/writing-a-document/getting-started/) — the five calls that make a PDF:
`New` → `AddPage` → `SetFont` → `Text` → `Write`.
-- [Graphics](graphics.md) — paths, colour, line state, transforms, clipping
+- [Graphics](/docs/writing-a-document/graphics/) — paths, colour, line state, transforms, clipping
and transparency.
-- [Text & fonts](text-and-fonts.md) — loading fonts, drawing text, measuring
+- [Text & fonts](/docs/writing-a-document/text-and-fonts/) — loading fonts, drawing text, measuring
and wrapping, and the shaped-text API for complex scripts.
-- [Images](images.md) — `DrawImage`, `DrawPNG` and `DrawJPEG`.
-- [Widgets](widgets.md) — printing a go-widgets/toolkit widget tree to a page.
-- [API reference](api.md) — the full public surface with Go examples.
+- [Images](/docs/writing-a-document/images/) — `DrawImage`, `DrawPNG` and `DrawJPEG`.
+- [Widgets](/docs/writing-a-document/widgets/) — printing a go-widgets/toolkit widget tree to a page.
+- [API reference](/docs/api/) — the full public surface with Go examples.
Source lives at
[github.com/go-pdfkit/pdfkit](https://github.com/go-pdfkit/pdfkit).
diff --git a/docs/api.md b/content/docs/api.md
similarity index 98%
rename from docs/api.md
rename to content/docs/api.md
index 3ee519e..5f499d1 100644
--- a/docs/api.md
+++ b/content/docs/api.md
@@ -1,4 +1,7 @@
-# API reference
+---
+title: "API reference"
+weight: 130
+---
The full public surface of `github.com/go-pdfkit/pdfkit`, the document
builder. See [pkg.go.dev](https://pkg.go.dev/github.com/go-pdfkit/pdfkit) for
@@ -157,7 +160,7 @@ lays the same tree out and instead emits PDF vector operators, so fills and
strokes stay crisp and text (including a TrueType-font widget label's own
embedded face) becomes real, selectable PDF text; it requires
`WidgetOptions.Font` and returns an error without one. See
-[Widgets](widgets.md) for a full walkthrough.
+[Widgets](/docs/writing-a-document/widgets/) for a full walkthrough.
## Example
diff --git a/content/docs/working-with-a-file/_index.md b/content/docs/working-with-a-file/_index.md
new file mode 100644
index 0000000..a127149
--- /dev/null
+++ b/content/docs/working-with-a-file/_index.md
@@ -0,0 +1,4 @@
+---
+title: "Working with a file"
+weight: 100
+---
diff --git a/docs/coediting.md b/content/docs/working-with-a-file/coediting.md
similarity index 98%
rename from docs/coediting.md
rename to content/docs/working-with-a-file/coediting.md
index b2578db..2b27ae6 100644
--- a/docs/coediting.md
+++ b/content/docs/working-with-a-file/coediting.md
@@ -1,4 +1,7 @@
-# Editing together
+---
+title: "Editing together"
+weight: 60
+---
[`coedit`](https://github.com/go-pdfkit/coedit) is a PDF several people are
editing at once.
diff --git a/docs/extraction.md b/content/docs/working-with-a-file/extraction.md
similarity index 97%
rename from docs/extraction.md
rename to content/docs/working-with-a-file/extraction.md
index 8dfe6fb..a3570fc 100644
--- a/docs/extraction.md
+++ b/content/docs/working-with-a-file/extraction.md
@@ -1,4 +1,7 @@
-# Reading a page back
+---
+title: "Reading a page back"
+weight: 50
+---
[`extract`](https://github.com/go-pdfkit/extract) reads a page as words and
pictures.
diff --git a/docs/operations.md b/content/docs/working-with-a-file/operations.md
similarity index 97%
rename from docs/operations.md
rename to content/docs/working-with-a-file/operations.md
index 4ce6367..5afbb85 100644
--- a/docs/operations.md
+++ b/content/docs/working-with-a-file/operations.md
@@ -1,4 +1,7 @@
-# Rearranging a file
+---
+title: "Rearranging a file"
+weight: 30
+---
[`ops`](https://github.com/go-pdfkit/ops) is the verb layer: what people
actually do to a PDF they already have. A document here is an ordered list of
diff --git a/docs/reading.md b/content/docs/working-with-a-file/reading.md
similarity index 98%
rename from docs/reading.md
rename to content/docs/working-with-a-file/reading.md
index d012a86..650fd69 100644
--- a/docs/reading.md
+++ b/content/docs/working-with-a-file/reading.md
@@ -1,4 +1,7 @@
-# Reading a file
+---
+title: "Reading a file"
+weight: 20
+---
[`reader`](https://github.com/go-pdfkit/reader) is the parsing and writing
half: everything about the format itself, and nothing about what a page
diff --git a/docs/rendering.md b/content/docs/working-with-a-file/rendering.md
similarity index 98%
rename from docs/rendering.md
rename to content/docs/working-with-a-file/rendering.md
index f60ba0d..9b9e300 100644
--- a/docs/rendering.md
+++ b/content/docs/working-with-a-file/rendering.md
@@ -1,4 +1,7 @@
-# Drawing a page
+---
+title: "Drawing a page"
+weight: 40
+---
[`render`](https://github.com/go-pdfkit/render) turns a page into pixels. It
reads with `reader`, rasterises with [go-gfx](https://github.com/go-gfx/gfx),
diff --git a/docs/workbench.md b/content/docs/working-with-a-file/workbench.md
similarity index 96%
rename from docs/workbench.md
rename to content/docs/working-with-a-file/workbench.md
index c28d610..8f27d98 100644
--- a/docs/workbench.md
+++ b/content/docs/working-with-a-file/workbench.md
@@ -1,4 +1,7 @@
-# The browser workbench
+---
+title: "The browser workbench"
+weight: 70
+---
[`app`](https://github.com/go-pdfkit/app) is a PDF workbench that runs in a
browser tab and nowhere else.
diff --git a/content/docs/writing-a-document/_index.md b/content/docs/writing-a-document/_index.md
new file mode 100644
index 0000000..895bccb
--- /dev/null
+++ b/content/docs/writing-a-document/_index.md
@@ -0,0 +1,4 @@
+---
+title: "Writing a document"
+weight: 200
+---
diff --git a/docs/getting-started.md b/content/docs/writing-a-document/getting-started.md
similarity index 93%
rename from docs/getting-started.md
rename to content/docs/writing-a-document/getting-started.md
index bad1814..b2f1966 100644
--- a/docs/getting-started.md
+++ b/content/docs/writing-a-document/getting-started.md
@@ -1,4 +1,7 @@
-# Getting started
+---
+title: "Getting started"
+weight: 80
+---
## Install
@@ -92,5 +95,5 @@ snapshot testing. Set `Options.Now` to a `func() time.Time` to stamp
`/CreationDate` and `/ModDate`; leave it `nil` to keep output reproducible.
`Options.ID` can also supply an explicit trailer `/ID` pair.
-Next: [Graphics](graphics.md) for paths and colour, or
-[Text & fonts](text-and-fonts.md) for embedding and shaping.
+Next: [Graphics](/docs/writing-a-document/graphics/) for paths and colour, or
+[Text & fonts](/docs/writing-a-document/text-and-fonts/) for embedding and shaping.
diff --git a/docs/graphics.md b/content/docs/writing-a-document/graphics.md
similarity index 93%
rename from docs/graphics.md
rename to content/docs/writing-a-document/graphics.md
index 2577d9a..d994248 100644
--- a/docs/graphics.md
+++ b/content/docs/writing-a-document/graphics.md
@@ -1,4 +1,7 @@
-# Graphics
+---
+title: "Graphics"
+weight: 90
+---
`Page`'s drawing methods mirror PDF's own imaging-model operators closely —
each method name maps to one or two content-stream operators, listed in
@@ -102,5 +105,5 @@ p.Fill()
p.Restore()
```
-Next: [Text & fonts](text-and-fonts.md) for embedding and drawing text, or
-[Images](images.md) for placing JPEG/PNG artwork.
+Next: [Text & fonts](/docs/writing-a-document/text-and-fonts/) for embedding and drawing text, or
+[Images](/docs/writing-a-document/images/) for placing JPEG/PNG artwork.
diff --git a/docs/images.md b/content/docs/writing-a-document/images.md
similarity index 93%
rename from docs/images.md
rename to content/docs/writing-a-document/images.md
index 1e9b027..dcf7eba 100644
--- a/docs/images.md
+++ b/content/docs/writing-a-document/images.md
@@ -1,4 +1,7 @@
-# Images
+---
+title: "Images"
+weight: 110
+---
Three methods place raster artwork into a `Rect` given in points (the
rectangle's `X`/`Y` is its lower-left corner, matching PDF's coordinate
@@ -70,5 +73,5 @@ All three methods place the image with the same graphics-state sequence:
of its native pixel dimensions. Each distinct image is registered once per
document as an `/Im` XObject resource, even if drawn on multiple pages.
-Next: the [API reference](api.md) for the complete signature list, or back to
-[Text & fonts](text-and-fonts.md).
+Next: the [API reference](/docs/api/) for the complete signature list, or back to
+[Text & fonts](/docs/writing-a-document/text-and-fonts/).
diff --git a/docs/text-and-fonts.md b/content/docs/writing-a-document/text-and-fonts.md
similarity index 96%
rename from docs/text-and-fonts.md
rename to content/docs/writing-a-document/text-and-fonts.md
index fbac026..56a8875 100644
--- a/docs/text-and-fonts.md
+++ b/content/docs/writing-a-document/text-and-fonts.md
@@ -1,4 +1,7 @@
-# Text & fonts
+---
+title: "Text & fonts"
+weight: 100
+---
## Loading a font
@@ -132,5 +135,5 @@ go-opentype for parsing, metrics and subsetting alike.
- Encryption, tagged PDF / PDF-A, interactive forms and annotations are not
yet implemented.
-Next: [Images](images.md) for placing JPEG/PNG artwork, or the
-[API reference](api.md) for the complete signature list.
+Next: [Images](/docs/writing-a-document/images/) for placing JPEG/PNG artwork, or the
+[API reference](/docs/api/) for the complete signature list.
diff --git a/docs/widgets.md b/content/docs/writing-a-document/widgets.md
similarity index 96%
rename from docs/widgets.md
rename to content/docs/writing-a-document/widgets.md
index ec7961e..399012e 100644
--- a/docs/widgets.md
+++ b/content/docs/writing-a-document/widgets.md
@@ -1,4 +1,7 @@
-# Widgets
+---
+title: "Widgets"
+weight: 120
+---
`Page.AddWidget` and `Page.AddWidgetVector` "print" a
[go-widgets/toolkit](https://github.com/go-widgets/toolkit) widget tree onto a
@@ -82,4 +85,4 @@ rect.Y -= pdfkit.Mm(40)
_ = p.AddWidgetVector(root, rect, &pdfkit.WidgetOptions{Font: font}) // crisp + selectable
```
-Next: the [API reference](api.md) for the complete signature list.
+Next: the [API reference](/docs/api/) for the complete signature list.
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..6ce7174
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,13 @@
+module github.com/go-pdfkit/docs
+
+go 1.27.1
+
+require github.com/imfing/hextra v0.13.0
+
+// ⛔ The theme is OUR FORK, not upstream. It keeps upstream's module path, so
+// it is reached through a replace rather than by importing a different path —
+// and it is tagged independently and ahead: v0.16.0 against upstream's v0.13.0.
+//
+// go-pdfkit/docs is the FIRST repository in the fleet to consume it, so this
+// is the pattern the others will copy.
+replace github.com/imfing/hextra => github.com/tannevaled/hextra v0.16.0
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..c5792f5
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,2 @@
+github.com/tannevaled/hextra v0.16.0 h1:BAULSdZDgiIwRxsXDxgqpZW0VN+vpbN5K95vyAgMpSs=
+github.com/tannevaled/hextra v0.16.0/go.mod h1:vi+yhpq8YPp/aghvJlNKVnJKcPJ/VyAEcfC1BSV9ARo=
diff --git a/hugo.toml b/hugo.toml
new file mode 100644
index 0000000..5c08ce4
--- /dev/null
+++ b/hugo.toml
@@ -0,0 +1,65 @@
+baseURL = "https://go-pdfkit.github.io/docs/"
+title = "go-pdfkit"
+languageCode = "en"
+
+# ⛔ Hugo + Hextra, and no Python. What this replaced was MkDocs Material plus
+# mike, which needed a Python toolchain, a requirements.txt and a pip install
+# in CI to render a static site for a pure-Go organisation.
+[module]
+ [[module.imports]]
+ path = "github.com/imfing/hextra"
+
+enableGitInfo = true
+
+[markup.highlight]
+ noClasses = false
+
+[markup.goldmark.renderer]
+ unsafe = true
+
+[params]
+ description = "The whole of PDF in pure Go with no C anywhere: read a file, write one, rearrange it, draw it, read it back as words and pictures, and edit it with other people — in a browser tab."
+
+ [params.navbar]
+ displayTitle = true
+ displayLogo = true
+ [params.navbar.logo]
+ path = "images/logo.svg"
+ width = 24
+ height = 24
+
+ [params.footer]
+ enable = true
+ displayCopyright = true
+ displayPoweredBy = false
+
+ [params.editURL]
+ enable = true
+ base = "https://github.com/go-pdfkit/docs/edit/main/content"
+
+[[menu.main]]
+ name = "Documentation"
+ pageRef = "/docs"
+ weight = 1
+
+[[menu.main]]
+ name = "Workbench"
+ url = "https://go-pdfkit.github.io/app/"
+ weight = 2
+
+[[menu.main]]
+ name = "Organisation"
+ url = "https://github.com/go-pdfkit"
+ weight = 3
+
+[[menu.main]]
+ name = "Search"
+ weight = 4
+ [menu.main.params]
+ type = "search"
+
+[[menu.main]]
+ weight = 5
+ url = "https://github.com/go-pdfkit"
+ [menu.main.params]
+ icon = "github"
diff --git a/mkdocs.yml b/mkdocs.yml
deleted file mode 100644
index 804daba..0000000
--- a/mkdocs.yml
+++ /dev/null
@@ -1,70 +0,0 @@
-site_name: go-pdfkit docs
-site_description: "The whole of PDF in pure Go with no C anywhere: read a file, write one, rearrange it, draw it, read it back as words and pictures, and edit it with other people — in a browser tab."
-site_url: https://go-pdfkit.github.io/docs/
-repo_url: https://github.com/go-pdfkit
-repo_name: go-pdfkit
-edit_uri: ""
-
-theme:
- name: material
- logo: assets/logo.svg
- favicon: assets/logo.svg
- features:
- - navigation.sections
- - navigation.top
- - navigation.instant
- - content.code.copy
- - search.suggest
- palette:
- # System default (follows the OS preference), then explicit light and dark.
- - media: "(prefers-color-scheme)"
- toggle:
- icon: material/brightness-auto
- name: Switch to light mode
- - media: "(prefers-color-scheme: light)"
- scheme: default
- primary: red
- toggle:
- icon: material/brightness-7
- name: Switch to dark mode
- - media: "(prefers-color-scheme: dark)"
- scheme: slate
- primary: red
- toggle:
- icon: material/brightness-4
- name: Switch to system preference
-
-extra:
- version:
- provider: mike
- default: latest
-
-nav:
- - Overview: index.md
- - Working with a file:
- - Reading a file: reading.md
- - Rearranging a file: operations.md
- - Drawing a page: rendering.md
- - Reading a page back: extraction.md
- - Editing together: coediting.md
- - The browser workbench: workbench.md
- - Writing a document:
- - Getting started: getting-started.md
- - Graphics: graphics.md
- - Text & fonts: text-and-fonts.md
- - Images: images.md
- - Widgets: widgets.md
- - API reference: api.md
-
-markdown_extensions:
- - admonition
- - attr_list
- - def_list
- - footnotes
- - tables
- - toc:
- permalink: true
- - pymdownx.highlight:
- anchor_linenums: true
- - pymdownx.superfences
- - pymdownx.inlinehilite
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index 34eb296..0000000
--- a/requirements.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-mkdocs-material==9.7.7
-mike==2.2.0
diff --git a/docs/assets/logo.svg b/static/images/logo.svg
similarity index 100%
rename from docs/assets/logo.svg
rename to static/images/logo.svg