Skip to content

Instantly share code, notes, and snippets.

@mcanouil
Created May 25, 2026 22:04
Show Gist options
  • Select an option

  • Save mcanouil/068cd6fa7976e0918f85fdc61390e3e7 to your computer and use it in GitHub Desktop.

Select an option

Save mcanouil/068cd6fa7976e0918f85fdc61390e3e7 to your computer and use it in GitHub Desktop.
Carousel: 2026-05-25 — Quarto Offset Headings v0.1.0
// Carousel: 2026-05-25 — Quarto Offset Headings v0.1.0
#set document(title: "Quarto Offset Headings v0.1.0", author: "Mickaël Canouil")
#set text(lang: "en")
#set page(width: 21cm, height: 18cm, margin: 0cm)
// ── palette ──────────────────────────────────────────────────────────────
#let bone = rgb("#f3f1ea")
#let bone-2 = rgb("#e8e5db")
#let ink = rgb("#19222b")
#let ink-soft = rgb("#5c6772")
#let line = rgb("#c7ccd2")
#let vermilion = rgb("#e8431f")
#let slab = rgb("#141b22")
#let slab-ink = rgb("#eef1f4")
#let slab-soft = rgb("#7e8b97")
#let slab-accent = rgb("#ff7a52")
// ── fonts ──────────────────────────────────────────────────────────────────
#let display = "Space Grotesk"
#let body = "Inter"
#let mono = "JetBrains Mono"
#set text(font: body, fill: ink)
// ── helpers ──────────────────────────────────────────────────────────────
// Slide wrapper: full page with generous padding.
#let slide(fill: bone, body) = page(fill: fill, {
block(width: 100%, height: 100%, inset: (x: 1.7cm, y: 1.7cm), body)
})
// Footer line: "project · version · date", pinned to the bottom of a slide.
#let footer(dark: false) = place(
bottom + left,
dy: 0.7cm,
text(
font: mono,
size: 10pt,
fill: if dark { slab-soft } else { ink-soft },
tracking: 0.4pt,
"mcanouil · offset-headings v0.1.0",
),
)
// A single hash glyph row at a given depth (1..6), drawn in mono.
#let hashrow(depth, label: none, active: false, dark: false) = {
let mark = "#" * depth
let mc = if active { vermilion } else if dark { slab-soft } else { ink-soft }
let lc = if active { if dark { slab-ink } else { ink } } else if dark { slab-soft } else { ink-soft }
grid(
columns: (2.6cm, 1fr),
align: (left + horizon, left + horizon),
text(font: mono, size: 17pt, weight: if active { "bold" } else { "regular" }, fill: mc, mark),
text(font: body, size: 15pt, weight: if active { "semibold" } else { "regular" }, fill: lc, label),
)
}
// Level gauge: a vertical rail labelled H1..H6 with one level lit.
#let gauge(lit: 1, dark: false) = {
let rc = if dark { slab-soft } else { line }
let tc = if dark { slab-soft } else { ink-soft }
stack(
dir: ttb,
spacing: 0.42cm,
..range(1, 7).map(n => {
let on = n == lit
box(
grid(
columns: (auto, 0.5cm),
align: (right + horizon, center + horizon),
column-gutter: 0.35cm,
text(
font: mono,
size: 12pt,
weight: if on { "bold" } else { "regular" },
fill: if on { vermilion } else { tc },
"H" + str(n),
),
box(
width: 0.5cm,
height: 0.5cm,
radius: 50%,
fill: if on { vermilion } else { rgb(0, 0, 0, 0) },
stroke: if on { none } else { 1.5pt + rc },
),
),
)
}),
)
}
// Code slab: dark cool block, light mono text.
#let codeslab(body) = block(
width: 100%,
fill: slab,
inset: (x: 22pt, y: 20pt),
radius: 12pt,
text(font: mono, size: 14pt, fill: slab-ink, body),
)
// Small eyebrow tag.
#let eyebrow(t, fill: vermilion) = text(
font: mono,
size: 12pt,
weight: "medium",
fill: fill,
tracking: 2pt,
upper(t),
)
// ════════════════════════════════════════════════════════════════════════
// SLIDE 1 — cover
// ════════════════════════════════════════════════════════════════════════
#slide({
// Faint oversized hash ladder as a background motif, top-right.
place(
top + right,
dx: 1.1cm,
dy: -0.6cm,
text(font: mono, size: 150pt, fill: bone-2, weight: "bold", tracking: -6pt, "###"),
)
place(top + left, eyebrow("Quarto extension · v0.1.0"))
place(
left + horizon,
dy: -1.2cm,
block(width: 15cm, {
text(font: display, size: 62pt, weight: "bold", fill: ink, tracking: -1pt, "Offset")
linebreak()
text(font: display, size: 62pt, weight: "bold", fill: vermilion, tracking: -1pt, "Headings")
v(0.5cm)
block(width: 13cm, text(
font: body,
size: 20pt,
fill: ink-soft,
"Shift heading levels by any amount, in any output format. As a filter, not a final pass.",
))
}),
)
// A small shift cue near the bottom: ## slides to ###.
place(bottom + left, dy: -1.5cm, {
grid(
columns: (auto, auto, auto),
column-gutter: 0.5cm,
align: horizon,
text(font: mono, size: 26pt, fill: ink-soft, weight: "bold", "##"),
text(font: mono, size: 22pt, fill: vermilion, "──▶"),
text(font: mono, size: 26pt, fill: vermilion, weight: "bold", "###"),
)
})
footer()
})
// ════════════════════════════════════════════════════════════════════════
// SLIDE 2 — the gap
// ════════════════════════════════════════════════════════════════════════
#slide({
eyebrow("The gap")
v(0.5cm)
block(width: 16cm, text(
font: display,
size: 33pt,
weight: "bold",
fill: ink,
"Pandoc shifts headings last.",
))
v(0.4cm)
block(width: 16cm, text(
font: body,
size: 19pt,
fill: ink-soft,
"'shift-heading-level-by' runs as a final post-processing step. By then every other extension has already seen the original levels and moved on.",
))
v(0.9cm)
grid(
columns: (1fr, 1fr),
column-gutter: 0.8cm,
block(
fill: bone-2,
width: 100%,
radius: 12pt,
inset: 18pt,
{
eyebrow("Built-in", fill: ink-soft)
v(0.3cm)
text(font: body, size: 16pt, fill: ink, "One number for the whole document. Applied at the end. No extension can react.")
},
),
block(
fill: slab,
width: 100%,
radius: 12pt,
inset: 18pt,
{
eyebrow("This extension", fill: slab-accent)
v(0.3cm)
text(font: body, size: 16pt, fill: slab-ink, "Runs mid-filter. Per heading or per document. Other filters see the shifted levels.")
},
),
)
footer()
})
// ════════════════════════════════════════════════════════════════════════
// SLIDE 3 — document-level offset
// ════════════════════════════════════════════════════════════════════════
#slide({
eyebrow("Whole document")
v(0.5cm)
block(width: 16cm, text(
font: display,
size: 31pt,
weight: "bold",
fill: ink,
"One key shifts everything.",
))
v(0.7cm)
grid(
columns: (1fr, 3.2cm),
column-gutter: 1cm,
align: (left, center),
{
codeslab[```yaml
extensions:
offset-headings:
by: 1
```]
v(0.6cm)
stack(
dir: ttb,
spacing: 0.5cm,
hashrow(1, label: "Section → becomes H2", active: true),
hashrow(2, label: "Sub-section → becomes H3", active: true),
hashrow(3, label: "Levels clamp at 1 and 6"),
)
},
{
eyebrow("by: +1", fill: vermilion)
v(0.3cm)
gauge(lit: 2)
},
)
footer()
})
// ════════════════════════════════════════════════════════════════════════
// SLIDE 4 — per-heading control
// ════════════════════════════════════════════════════════════════════════
#slide(fill: slab, {
text(font: mono, size: 12pt, weight: "medium", fill: slab-accent, tracking: 2pt, upper("Per heading"))
v(0.5cm)
block(width: 16cm, text(
font: display,
size: 31pt,
weight: "bold",
fill: slab-ink,
"Or target a single heading.",
))
v(0.7cm)
codeslab[```markdown
## Methods {offset-headings-by="2"}
### Sampling
```]
v(0.6cm)
block(width: 16cm, text(
font: body,
size: 18pt,
fill: slab-soft,
"By default the offset cascades to nested headings. Set 'offset-headings-recursive=\"false\"' to move just that one and leave its children where they are.",
))
v(0.7cm)
grid(
columns: (1fr, 1fr),
column-gutter: 0.8cm,
{
eyebrow("recursive: true", fill: slab-accent)
v(0.25cm)
stack(dir: ttb, spacing: 0.32cm,
hashrow(2, label: "Methods → H4", active: true, dark: true),
hashrow(3, label: "Sampling → H5", active: true, dark: true),
)
},
{
eyebrow("recursive: false", fill: slab-soft)
v(0.25cm)
stack(dir: ttb, spacing: 0.32cm,
hashrow(2, label: "Methods → H4", active: true, dark: true),
hashrow(3, label: "Sampling → H3", dark: true),
)
},
)
footer(dark: true)
})
// ════════════════════════════════════════════════════════════════════════
// SLIDE 5 — cross-format, paired with quarto-prism
// ════════════════════════════════════════════════════════════════════════
#slide({
eyebrow("Cross-format")
v(0.5cm)
block(width: 16.5cm, text(
font: display,
size: 30pt,
weight: "bold",
fill: ink,
"Same heading, two meanings.",
))
v(0.4cm)
block(width: 16.5cm, text(
font: body,
size: 18pt,
fill: ink-soft,
"In Reveal.js, '# Part' opens a slide section. In an HTML article you usually want it nested under the page title instead.",
))
v(0.6cm)
codeslab[```markdown
# Part {html:offset-headings-by="1"}
## Topic
```]
v(0.35cm)
text(font: mono, size: 13pt, fill: ink-soft, [Prism gates the attribute: it applies in HTML only. #text(fill: vermilion)[github.com/mcanouil/quarto-prism]])
v(0.7cm)
grid(
columns: (1fr, 1fr),
column-gutter: 0.8cm,
{
eyebrow("revealjs · offset ignored", fill: ink-soft)
v(0.25cm)
stack(dir: ttb, spacing: 0.32cm,
hashrow(1, label: "Part → H1 · slide part"),
hashrow(2, label: "Topic → H2 · slide"),
)
},
{
eyebrow("html · +1 applied", fill: vermilion)
v(0.25cm)
stack(dir: ttb, spacing: 0.32cm,
hashrow(1, label: "Part → H2", active: true),
hashrow(2, label: "Topic → H3", active: true),
)
},
)
footer()
})
// ════════════════════════════════════════════════════════════════════════
// SLIDE 6 — why it matters
// ════════════════════════════════════════════════════════════════════════
#slide({
eyebrow("Why it matters")
v(0.5cm)
block(width: 16.5cm, text(
font: display,
size: 31pt,
weight: "bold",
fill: ink,
"Compose documents without rewriting headings.",
))
v(0.8cm)
let row(n, t) = grid(
columns: (1.4cm, 1fr),
align: (left + top, left + top),
text(font: mono, size: 22pt, weight: "bold", fill: vermilion, str(n)),
block(width: 14.5cm, text(font: body, size: 18pt, fill: ink, t)),
)
stack(
dir: ttb,
spacing: 0.7cm,
row(1, "Drop a standalone chapter into a report as a section. Push every heading down one level, no manual edits."),
row(2, "Pull an included file up a level when it sits at the top of its own page."),
row(3, "Stays format-agnostic: HTML, PDF, Typst, docx all honour the same shift."),
row(4, "Composes with other filters, because the levels change before they run."),
)
footer()
})
// ════════════════════════════════════════════════════════════════════════
// SLIDE 7 — ship
// ════════════════════════════════════════════════════════════════════════
#slide(fill: slab, {
place(
top + right,
dx: 1.0cm,
dy: -0.7cm,
text(font: mono, size: 130pt, fill: rgb("#1c2630"), weight: "bold", tracking: -6pt, "#"),
)
text(font: mono, size: 12pt, weight: "medium", fill: slab-accent, tracking: 2pt, upper("Ship it"))
v(0.6cm)
block(width: 16cm, text(
font: display,
size: 40pt,
weight: "bold",
fill: slab-ink,
"Add it in one line.",
))
v(0.8cm)
codeslab[```bash
quarto add mcanouil/quarto-offset-headings
```]
v(1.1cm)
stack(
dir: ttb,
spacing: 0.42cm,
text(font: body, size: 17pt, fill: slab-ink, [GitHub: #text(fill: slab-accent)[github.com/mcanouil/quarto-offset-headings]]),
text(font: body, size: 17pt, fill: slab-soft, "MIT licensed · v0.1.0 · works in any Quarto output format"),
)
footer(dark: true)
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment