diff --git a/.claude/skills/blog-post/SKILL.md b/.claude/skills/blog-post/SKILL.md
index be03481e5..15875c9f7 100644
--- a/.claude/skills/blog-post/SKILL.md
+++ b/.claude/skills/blog-post/SKILL.md
@@ -59,7 +59,9 @@ Guest authors use structured form with `name:` and optional `url:`.
`Tip`, `Extensions`, `Tables`, `Teaching`, `Jupyter`.
**image / image-alt**: `thumbnail.png` preferred. `image-alt` is mandatory.
-See `references/thumbnail-guide.md` for dimensions, visual style, and design patterns.
+See `references/thumbnail-guide.md` for the decision between the two production
+paths (Typst, or HTML+SVG) and the HTML+SVG flow. For the Typst path, see
+`references/typst-thumbnail.md`.
**Optional**: `lightbox: true` (many screenshots), `draft: true` (while developing).
Do not use `subtitle:` (phased out after 2023).
@@ -139,7 +141,16 @@ Use sparingly: `.callout-tip` for post origin context, `.callout-warning` for ca
4. **Draft body** following type-specific structure
5. **Add images** with `fig-alt=` on every one
6. **Link to docs** for every feature mentioned
-7. **Create thumbnail** (or note one is needed)
+7. **Create thumbnail**:
+ 1. Read `references/thumbnail-guide.md` § Choosing your path
+ 2. **Always present both options as a question to the user**, even if one
+ path is the obvious recommendation. State the recommendation with
+ reasoning (logos? programmatic diagram? text-only? release post?), then
+ explicitly ask the user to confirm or override before proceeding. Never
+ silently pick.
+ 3. Read the chosen path's reference and execute
+ (`references/typst-thumbnail.md` for Typst, rest of `thumbnail-guide.md`
+ for HTML+SVG)
8. **Review**: direct opener? code blocks fenced with language? all images alt-texted?
docs links present? closing matches type convention? categories correct?
diff --git a/.claude/skills/blog-post/assets/quarto-logo-trademark-light.svg b/.claude/skills/blog-post/assets/quarto-logo-trademark-light.svg
new file mode 100644
index 000000000..8f825326b
--- /dev/null
+++ b/.claude/skills/blog-post/assets/quarto-logo-trademark-light.svg
@@ -0,0 +1,40 @@
+
+
diff --git a/.claude/skills/blog-post/assets/quarto-logo-trademark.svg b/.claude/skills/blog-post/assets/quarto-logo-trademark.svg
new file mode 100644
index 000000000..4f38424b4
--- /dev/null
+++ b/.claude/skills/blog-post/assets/quarto-logo-trademark.svg
@@ -0,0 +1,40 @@
+
+
diff --git a/.claude/skills/blog-post/assets/thumbnail-diagram.typ b/.claude/skills/blog-post/assets/thumbnail-diagram.typ
new file mode 100644
index 000000000..f5299389e
--- /dev/null
+++ b/.claude/skills/blog-post/assets/thumbnail-diagram.typ
@@ -0,0 +1,56 @@
+#import "@preview/cetz:0.3.4"
+
+#set page(
+ width: 1200pt,
+ height: 630pt,
+ margin: 50pt,
+ fill: rgb("#447099"),
+)
+
+#set text(
+ fill: white,
+ font: ("Helvetica Neue", "Helvetica", "Arial", "Liberation Sans", "DejaVu Sans"),
+)
+
+#align(center)[
+ #stack(
+ dir: ltr,
+ spacing: 16pt,
+ align(horizon)[#image("quarto-logo-trademark-light.svg", height: 90pt)],
+ align(horizon)[#text(baseline: -8pt, size: 90pt, weight: "bold")[2]],
+ )
+
+ #v(10pt)
+ #text(size: 56pt, weight: "bold")[Replace this title]
+
+ #v(10pt)
+
+ #cetz.canvas(length: 45pt, {
+ import cetz.draw: *
+
+ // Ellipse node — white fill, dark blue label.
+ let node(pos, label, name) = {
+ let (x, y) = pos
+ circle(
+ (x, y),
+ radius: (1.4, 0.55),
+ fill: white,
+ stroke: white,
+ name: name,
+ )
+ content((x, y), text(fill: rgb("#1f3a52"), weight: "bold", size: 20pt)[#label])
+ }
+
+ // Edge — white line connecting two named nodes.
+ let edge(a, b) = {
+ line(a, b, stroke: (paint: white, thickness: 1.5pt))
+ }
+
+ // Replace the placeholder graph below with your own nodes + edges.
+ node((4, 2), "Root", "root")
+ node((1, 0), "A", "a")
+ node((7, 0), "B", "b")
+ edge("root", "a")
+ edge("root", "b")
+ })
+]
diff --git a/.claude/skills/blog-post/assets/thumbnail-text.typ b/.claude/skills/blog-post/assets/thumbnail-text.typ
new file mode 100644
index 000000000..c8a43c7e4
--- /dev/null
+++ b/.claude/skills/blog-post/assets/thumbnail-text.typ
@@ -0,0 +1,26 @@
+#set page(
+ width: 1200pt,
+ height: 630pt,
+ margin: 50pt,
+ fill: rgb("#447099"),
+)
+
+#set text(
+ fill: white,
+ font: ("Helvetica Neue", "Helvetica", "Arial", "Liberation Sans", "DejaVu Sans"),
+)
+
+#align(center + horizon)[
+ #stack(
+ dir: ltr,
+ spacing: 16pt,
+ align(horizon)[#image("quarto-logo-trademark-light.svg", height: 90pt)],
+ align(horizon)[#text(baseline: -8pt, size: 90pt, weight: "bold")[2]],
+ )
+
+ #v(20pt)
+ #text(size: 56pt, weight: "bold")[Replace this title]
+
+ #v(10pt)
+ #text(size: 32pt)[Replace this subtitle (optional)]
+]
diff --git a/.claude/skills/blog-post/references/thumbnail-guide.md b/.claude/skills/blog-post/references/thumbnail-guide.md
index 6d8007db8..dc174ddbb 100644
--- a/.claude/skills/blog-post/references/thumbnail-guide.md
+++ b/.claude/skills/blog-post/references/thumbnail-guide.md
@@ -2,6 +2,25 @@
Design conventions for blog post listing card images, derived from 40 existing posts.
+## Choosing your path
+
+Two production paths exist for thumbnails. Pick before you start designing.
+
+| Path | Use when | Avoid when |
+|------|----------|------------|
+| **Typst** ([`typst-thumbnail.md`](typst-thumbnail.md)) | Text-only thumbnails, programmatic diagrams (trees, graphs, flow), version/title cards | Design uses 3rd-party logos/icons with their own colors |
+| **HTML+SVG** (rest of this guide) | Icon compositions with 3rd-party SVGs, photographic content, complex CSS-only layouts | Pure text or programmatic diagrams (Typst is faster) |
+
+> **Release posts default to HTML+SVG.** The release thumbnail template (steel
+> blue + Quarto logo + version + small thematic emoji) is established convention
+> from 1.4 through current. Switching to Typst for a release is a stylistic
+> choice — confirm with the team before deviating. New release posts that match
+> the template should reuse the existing HTML+SVG flow so all release thumbnails
+> stay visually consistent.
+
+For Typst, see [`typst-thumbnail.md`](typst-thumbnail.md). The rest of this
+guide covers the HTML+SVG path.
+
## Dimensions and Format
- **Size**: 1200x630 px (Open Graph / social card standard)
@@ -72,7 +91,7 @@ Use Posit conference brand templates rather than the Quarto steel blue:
Use the partner's own logo on a white or neutral background (e.g., Hugging Face logo,
Confluence logo). No Quarto branding needed — the partner identity is the visual.
-## Creating Thumbnails
+## Creating Thumbnails (HTML+SVG path)
### Prerequisites
diff --git a/.claude/skills/blog-post/references/typst-thumbnail.md b/.claude/skills/blog-post/references/typst-thumbnail.md
new file mode 100644
index 000000000..b90ef0c7f
--- /dev/null
+++ b/.claude/skills/blog-post/references/typst-thumbnail.md
@@ -0,0 +1,231 @@
+# Typst Thumbnail Path
+
+How to create a blog post thumbnail with [Typst](https://typst.app), using the
+templates and assets bundled in this skill.
+
+The Typst path is good for **text-only** thumbnails (release-style title cards
+without emoji) and for **programmatic diagrams** (trees, flows, simple graphs).
+For thumbnails that compose third-party logos or icons, use the HTML+SVG path
+(see `thumbnail-guide.md`).
+
+## When to use Typst vs HTML+SVG
+
+See the `Choosing your path` table at the top of `thumbnail-guide.md`. In short:
+
+- Diagram explains the concept of the post → Typst with cetz
+- Plain title + version card → Typst (text-only template)
+- Composition of third-party logos/icons → HTML+SVG
+- Release post that matches the established template (logo + version + emoji) → HTML+SVG
+
+## Prerequisites
+
+Quarto bundles a Typst CLI. No separate install is needed.
+
+```powershell
+quarto typst --version
+```
+
+Before rendering, check what fonts are available so you understand any
+substitution that may happen:
+
+```powershell
+quarto typst fonts | Select-String -Pattern "Helvetica|Arial"
+```
+
+The most common silent failure is font substitution — the render succeeds but
+the visual differs from what you expected because the system fell back to a
+different font.
+
+## Setup
+
+Copy the template and the logo SVG from the skill's `assets/` directory into
+your post directory. Both files must live alongside `thumbnail.typ` because the
+template uses a co-located filename for the logo.
+
+```powershell
+$skill = ".claude/skills/blog-post/assets"
+$post = "docs/blog/posts/YYYY-MM-DD-slug"
+Copy-Item "$skill/thumbnail-diagram.typ" "$post/thumbnail.typ" # or thumbnail-text.typ
+Copy-Item "$skill/quarto-logo-trademark-light.svg" $post # white-fill, for #447099 / dark backgrounds
+```
+
+Two wordmark variants are bundled:
+
+| File | Use on |
+|------|--------|
+| `quarto-logo-trademark-light.svg` | Dark backgrounds (`#447099` blue, `#4D6E8E` steel) — default for the bundled templates |
+| `quarto-logo-trademark.svg` | Light backgrounds (`#F0F5F9`) — swap in if you adapt a template to the light palette |
+
+Both derive from the same brand-kit source; the only difference is `fill`.
+
+When the post is ready, commit all three artifacts together:
+
+- `thumbnail.typ` — the source
+- `thumbnail.png` — the rendered output (referenced by frontmatter `image:`)
+- `quarto-logo-trademark-light.svg` — the bundled wordmark
+
+This keeps the post self-contained: anyone with `quarto typst` can re-render
+the thumbnail on a fresh checkout.
+
+## Page setup
+
+Both bundled templates use:
+
+- Page size: `1200pt` × `630pt` (Open Graph / social card standard)
+- Margin: `50pt`
+- Background fill: `rgb("#447099")` — Quarto release-thumbnail blue
+
+Quarto brand palette (mirror of `thumbnail-guide.md`):
+
+| Hex | Use |
+|-----|-----|
+| `#447099` | Page background (release thumbnails) |
+| `#5286AB` | Quarto blue — accents on light backgrounds |
+| `#39729E` | Hero heading blue |
+| `#4D6E8E` | Steel blue — alternate background |
+| `#F0F5F9` | Hero light blue — alternate background |
+| `#1f3a52` | Dark text on white-filled cetz nodes |
+
+## Font fallback chain
+
+Both templates use:
+
+```typst
+font: ("Helvetica Neue", "Helvetica", "Arial", "Liberation Sans", "DejaVu Sans")
+```
+
+Helvetica Neue is preferred (matches existing release thumbnails) but is not
+present on Linux contributors' machines by default. The chain ensures a clean
+sans-serif substitution rather than a warning.
+
+## Cetz pin
+
+The diagram template pins cetz to `0.3.4`:
+
+```typst
+#import "@preview/cetz:0.3.4"
+```
+
+Cetz is pre-1.0 and its API is still evolving. Pinning a specific version means
+the template renders the same on every contributor's machine and survives
+upstream cetz releases unchanged. Bump the pin only when you intentionally
+adopt a new cetz feature and re-test all bundled templates.
+
+## Title injection
+
+Templates contain literal placeholder strings. Edit them directly:
+
+```diff
+- #text(size: 56pt, weight: "bold")[Replace this title]
++ #text(size: 56pt, weight: "bold")[Parsing & Source Maps]
+```
+
+No `--input` / `sys.inputs` indirection — literal edit keeps the template
+readable and the source file is the canonical record of what the rendered PNG
+shows.
+
+## Diagram scaffold pattern (cetz)
+
+The diagram template ships with two helpers preloaded inside `cetz.canvas`:
+
+```typst
+let node(pos, label, name) = { ... } // ellipse, white fill
+let edge(a, b) = { ... } // straight white line between named nodes
+```
+
+### Snippet 1 — three-node tree
+
+```typst
+node((4, 2), "Doc", "doc")
+node((1, 0), "Header", "header")
+node((7, 0), "Str", "str")
+edge("doc", "header")
+edge("doc", "str")
+```
+
+Position uses cetz coordinate units; `length: 45pt` on `cetz.canvas` scales
+units to layout points. Adjust coordinates to spread nodes out.
+
+### Snippet 2 — rectangle nodes with custom colors
+
+When ellipses are wrong (e.g., long labels, technical diagrams), define a
+second helper that draws rectangles:
+
+```typst
+let box(pos, label, name) = {
+ let (x, y) = pos
+ rect(
+ (x - 1.6, y - 0.5),
+ (x + 1.6, y + 0.5),
+ fill: rgb("#F0F5F9"),
+ stroke: white,
+ name: name,
+ )
+ content((x, y), text(fill: rgb("#1f3a52"), weight: "bold", size: 20pt)[#label])
+}
+
+box((0, 0), "input.qmd", "src")
+box((6, 0), "rendered.html", "out")
+```
+
+### Snippet 3 — directed edge with arrowhead
+
+Replace `edge` with a variant that draws an arrowhead:
+
+```typst
+let arrow(a, b) = {
+ line(a, b, mark: (end: ">"), stroke: (paint: white, thickness: 1.5pt))
+}
+
+arrow("src", "out")
+```
+
+For a label on the edge, place a `content()` at the midpoint:
+
+```typst
+content(((src.x + out.x) / 2, (src.y + out.y) / 2 + 0.4),
+ text(fill: white, size: 16pt)[render])
+```
+
+For richer cetz patterns (anchors, transformations, plot integration) consult
+the cetz manual at .
+
+## Render command
+
+Run from the post directory:
+
+```powershell
+cd docs/blog/posts/YYYY-MM-DD-slug
+quarto typst compile thumbnail.typ thumbnail.png -f png --ppi 144
+```
+
+`--ppi 144` produces a 2400×1260 PNG (twice the 1200×630 base) — the HiDPI/2x
+retina convention noted in `thumbnail-guide.md`.
+
+## Anti-patterns
+
+- **Don't use `--input` / `sys.inputs`** for the title. Literal-edit is simpler
+ and keeps the template grep-able.
+- **Don't unpin cetz.** A floating import will eventually break templates and
+ re-renders won't be reproducible.
+- **Don't omit the font fallback chain.** A bare `Helvetica Neue` produces a
+ warning and falls back to Typst's default, which may not be what you want.
+- **Don't ship a `.typ` without rendering.** The PNG is the artifact Quarto
+ serves; the source is for revision. Both must exist and stay in sync.
+- **Don't omit the logo SVG copy.** The template references
+ `quarto-logo-trademark-light.svg` as a co-located file. Without it, render
+ fails on a fresh checkout (this is exactly what happened to the original
+ `2026-05-05-quarto-2-parsing/thumbnail.typ`).
+
+## Optional global helpers
+
+Two third-party Claude skills cover Typst and cetz in depth and may help when
+you want general Typst guidance beyond the thumbnail templates:
+
+- [`lucifer1004/claude-skill-typst`](https://github.com/lucifer1004/claude-skill-typst)
+ — general Typst authoring, debug verification, package search
+- [`edoardob90/typst-cetz-skills`](https://github.com/edoardob90/typst-cetz-skills)
+ — cetz API reference (canvas, primitives, anchors, transforms)
+
+These are optional. The bundled templates and this guide are sufficient to ship
+a thumbnail without them.
diff --git a/docs/blog/posts/2026-05-05-quarto-2-parsing/quarto-logo-trademark-light.svg b/docs/blog/posts/2026-05-05-quarto-2-parsing/quarto-logo-trademark-light.svg
new file mode 100644
index 000000000..8f825326b
--- /dev/null
+++ b/docs/blog/posts/2026-05-05-quarto-2-parsing/quarto-logo-trademark-light.svg
@@ -0,0 +1,40 @@
+
+