# Schlussteil einer Dissertation: Anhang, Glossar und Register

> Die letzten Seiten einer Dissertation in Schwarzweiß: ein Anhang mit Buchstaben, ein zweispaltiges Glossar, APA-Literatur und ein Register aus Marken im Text.

- HTML-Fassung: https://postext.dev/de/cookbook/thesis-back-matter
- Rezept Nº 031 · Buchaufbau · Stufe 3 (Fortgeschritten) · Ausgaben: Canvas, PDF
- Gattungen: Fachartikel und Wissenschaft
- Benötigt postext ≥ 1.28.0, postext-pdf ≥ 1.28.0 · getestet mit 1.31.0, postext-pdf 1.31.0 am 2026-10-13
- Seiten: [171](https://postext.dev/cookbook/thesis-back-matter/en/p01.webp?v=6b02529f), [172](https://postext.dev/cookbook/thesis-back-matter/en/p02.webp?v=6b02529f), [173](https://postext.dev/cookbook/thesis-back-matter/en/p03.webp?v=6b02529f), [174](https://postext.dev/cookbook/thesis-back-matter/en/p04.webp?v=6b02529f), [175](https://postext.dev/cookbook/thesis-back-matter/en/p05.webp?v=6b02529f), [176](https://postext.dev/cookbook/thesis-back-matter/en/p06.webp?v=6b02529f), [177](https://postext.dev/cookbook/thesis-back-matter/en/p07.webp?v=6b02529f)
- PDF: https://postext.dev/cookbook/thesis-back-matter/en/thesis-back-matter.pdf?v=6b02529f
- In der Sandbox öffnen: https://postext.dev/de/sandbox#recipe=thesis-back-matter&lang=en (.postext: https://postext.dev/cookbook/thesis-back-matter/en/thesis-back-matter.postext)
- Zuletzt aktualisiert: 2026-10-11
- Andere Sprachen: [en](https://postext.dev/en/cookbook/thesis-back-matter.md), [es](https://postext.dev/es/cookbook/thesis-back-matter.md), [ca](https://postext.dev/ca/cookbook/thesis-back-matter.md), [fr](https://postext.dev/fr/cookbook/thesis-back-matter.md), [it](https://postext.dev/it/cookbook/thesis-back-matter.md), [pt](https://postext.dev/pt/cookbook/thesis-back-matter.md), [zh](https://postext.dev/zh/cookbook/thesis-back-matter.md), [ja](https://postext.dev/ja/cookbook/thesis-back-matter.md), [ar](https://postext.dev/ar/cookbook/thesis-back-matter.md)

## Kurz gesagt

Die letzten Seiten einer Doktorarbeit, vom Schlusskapitel bis zum Register. Postext baut das Register aus Wörtern, die im Text markiert sind, und ergänzt ihre Seitenzahlen.

## Was Sie setzen

Die letzten sieben Seiten einer Doktorarbeit über das Lesen am Bildschirm und auf Papier, auf einer B5-Seite in Schwarzweiß gesetzt. Kapitel 6, Anhang A, Glossar, Literaturverzeichnis und Register beginnen jeweils unter demselben schwarzen Band von 62 mm Höhe, aus dem der Titel weiß ausgespart ist. Das Kapitel zeigt im Band seine Ziffer und der Anhang seinen Buchstaben; die Abschnitte des Schlussteils tragen die Dachzeile „BACK MATTER“ und eine kurze kursive Notiz. Das Kapitel läuft in einer Spalte im Blocksatz. Glossar und Register wechseln zu zwei Spalten im Flattersatz, und das Literaturverzeichnis kehrt zu einer zurück, wobei die Folgezeilen jedes Eintrags eingezogen sind. Das Register entsteht aus Marken im Text: Die Engine sortiert die Begriffe unter ihren Buchstaben, findet die Seite jeder Marke und fasst aufeinanderfolgende Seiten zu Bereichen wie 171–73 zusammen. Fette Zahlen verweisen auf die Definitionen des Glossars.

**Dieses Rezept beantwortet:**

- Wie setze ich Glossar, Literatur und Register einer Dissertation mit hängendem Einzug und kleinerer Schrift?
- Wie erstelle ich ein Register, dessen Seitenzahlen sich von selbst ändern, wenn sich der Text verschiebt?
- Wie verhindere ich, dass Überschriften, fette Wörter und Aufzählungszeichen blau erscheinen?
- Wie nummeriere ich Überschriften (1, 1.1, 1.1.1) und gestalte jede Ebene anders?
- Wie setze ich Kolumnentitel: Buchtitel auf der linken Seite, Kapiteltitel auf der rechten, Seitenzahl außen?
- Wie erzwinge ich einen Seiten- oder Spaltenumbruch und lasse jedes Kapitel auf einer rechten Seite beginnen?

## Die kurze Antwort

```js
// script.js, Zeilen 27–51
// '# Glossary {style="glossary"}' in the Markdown picks a style. Each style starts a page
// of either parity, the appendix a recto (a style that sets no break inherits its level's
// 'odd': gotcha style-inherits-break), stays out of the chapter count (numbered: false, so
// its band has no numeral) and brings its own running heads; the glossary and the index
// set their pages in two columns until the next '#'. The appendix counts, in letters: its
// template prints the level's counter as A, B… config() takes both lists below.
const twoColumns = { layoutType: 'double', gutterWidth: mm(6) };
const backMatter = (id, extra) => ({ id, numbered: false, breakBefore: { enabled: true,
  parity: 'any' }, advancedDesign: opener('Back matter'), header: sectionHeads, ...extra });
const headingStyles = () => [
  backMatter('appendix', { numbered: true, numberingTemplate: '{1:A}', header: appendixHeads,
    breakBefore: { enabled: true, parity: 'odd' }, advancedDesign: opener('Appendix') }),
  backMatter('glossary', { layout: twoColumns }),
  backMatter('references'),
  backMatter('index', { layout: twoColumns }),
];
// One paragraph per entry, in :::paragraphs{style="…"}: the turnover lines hang, so the
// first word of every entry stands clear at the left. Ragged, as APA asks of references,
// with every word whole (hyphenation.ragged is off). The index has its own settings.
const entries = (id, size, lead, hang, extra) => ({ id, fontSize: pt(size),
  lineHeight: pt(lead), textAlign: 'left', hangingIndent: em(hang), ...extra });
const paragraphStyles = () => [
  entries('term', 9.3, 12.4, 1), // the glossary: a bold term, then its definition
  entries('reference', 9.3, 12.4, 1.5, { spaceBetween: pt(2.4) }),
];
```

## Zutaten

**Zeigt**

- [Abschnittsgeometrie](https://postext.dev/de/docs/configuration-styles.md#überschriftenstile): Ein Überschriftenformat, das Ränder und Spaltenlayout für seinen Abschnitt ändert, etwa ein einspaltiges Vorwort in einem zweispaltigen Buch.
- [Literaturverzeichnisse und Glossare](https://postext.dev/de/docs/configuration-styles.md#absatzformate): Literaturlisten und Glossare in kleinerer Schrift mit hängendem Einzug, ein Absatz pro Eintrag.
- [Kapitel ohne Nummer](https://postext.dev/de/docs/configuration-styles.md#überschriftenstile): Die Überschrift eines Vorworts, Anhangs oder Impressums, die keine Nummer erhält und die Kapitelzählung unberührt lässt.

**Verwendet außerdem**

- [Register](https://postext.dev/de/docs/document-format.md#register)
- [Kolumnentitel je Abschnitt](https://postext.dev/de/docs/configuration-styles.md#überschriftenstile)
- [Überschriftenformate](https://postext.dev/de/docs/configuration-styles.md#überschriftenstile)
- [Nummerierte Überschriften](https://postext.dev/de/docs/configuration-text.md#überschreibungen-pro-ebene)
- [Kapitel, die rechts beginnen](https://postext.dev/de/docs/configuration-text.md#umbruch-davor)
- [Kapitelband über die ganze Breite](https://postext.dev/de/docs/configuration-text.md#seitenbreite-und-erweiterte-gestaltung)
- [Gestaltete Auftakte](https://postext.dev/de/docs/configuration-text.md#seitenbreite-und-erweiterte-gestaltung)
- [Überschriftenattribute](https://postext.dev/de/docs/document-format.md#attribute-von-überschriften)
- [Kopfzeilen nach Seitenrolle](https://postext.dev/de/docs/configuration-page-layout.md#textelemente)
- [Absatzformate](https://postext.dev/de/docs/configuration-styles.md#absatzformate)
- [Eigene Ressourcentypen](https://postext.dev/de/docs/configuration-resources.md#ressourcentypen)
- [Verweise, die Abbildungen platzieren](https://postext.dev/de/docs/document-format.md#verweis-im-text-die-hauptform)
- [Tabellen aus Daten](https://postext.dev/de/docs/document-format.md#blockeinbettung-optional-ausdrückliche-platzierung-im-text)
- [Format der Bildunterschrift](https://postext.dev/de/docs/configuration-resources.md#legendenstil)
- [Semantische Farbpalette](https://postext.dev/de/docs/configuration-fonts-colors-viewers.md#farbpalette)
- [Fett, kursiv und ihre Farben](https://postext.dev/de/docs/configuration-text.md#fließtext)
- [PDF-Export](https://postext.dev/de/docs/configuration-programmatic-usage.md#pdfs-erzeugen)
- [Kästen](https://postext.dev/de/docs/configuration-styles.md#kastenstile)
- [Spaltenausgleich](https://postext.dev/de/docs/configuration-text.md#spaltenausgleich)
- [Abbildung und Tabelle in Ihrer Sprache](https://postext.dev/de/docs/configuration-resources.md#ressourcentypen)
- [Das Raster bewusst verlassen](https://postext.dev/de/docs/architecture.md#rasterbrechende-elemente)
- [Im PDF eingebettete Schriften](https://postext.dev/de/docs/configuration-programmatic-usage.md#warum-ein-font-provider)

**Die Konfiguration auf einen Blick**

- [`bodyText`](https://postext.dev/de/docs/configuration-text.md#fließtext), [`calloutStyles`](https://postext.dev/de/docs/configuration-styles.md#kastenstile), [`captionStyle`](https://postext.dev/de/docs/configuration-resources.md#legendenstil), [`colorPalette`](https://postext.dev/de/docs/configuration-fonts-colors-viewers.md#farbpalette), [`footer`](https://postext.dev/de/docs/configuration-page-layout.md#kopf--und-fußzeilen), [`header`](https://postext.dev/de/docs/configuration-page-layout.md#kopf--und-fußzeilen), [`headingStyles`](https://postext.dev/de/docs/configuration-styles.md#überschriftenstile), [`headings`](https://postext.dev/de/docs/configuration-text.md#überschriften), [`index`](https://postext.dev/de/docs/configuration-notes-references.md#register), [`layout`](https://postext.dev/de/docs/configuration-page-layout.md#layout), [`locale`](https://postext.dev/de/docs/configuration-text.md#silbentrennung), [`orderedLists`](https://postext.dev/de/docs/configuration-text.md#nummerierte-listen), [`page`](https://postext.dev/de/docs/configuration-page-layout.md#seite), [`paragraphStyles`](https://postext.dev/de/docs/configuration-styles.md#absatzformate), [`resourceTypes`](https://postext.dev/de/docs/configuration-resources.md#ressourcentypen), [`tableStyle`](https://postext.dev/de/docs/configuration-resources.md#tabellenstil), [`unorderedLists`](https://postext.dev/de/docs/configuration-text.md#aufzählungen)

**APIs**

- [`buildDocumentWithFonts`](https://postext.dev/de/docs/configuration-programmatic-usage.md#schriften-vor-dem-layout-laden), [`decompressWoff2`](https://postext.dev/de/docs/configuration-programmatic-usage.md#font-provider-im-browser-fontsource--woff2), [`defaultResourceTypes`](https://postext.dev/de/docs/configuration-resources.md#ressourcentypen), [`renderPageToCanvas`](https://postext.dev/de/docs/configuration-programmatic-usage.md#eine-seite-als-bitmap-rendern), [`renderToPdf`](https://postext.dev/de/docs/configuration-programmatic-usage.md#pdfs-erzeugen)

**Schriften**

- Libertinus Serif (OFL-1.1), Libertinus Serif Display (OFL-1.1), Libertinus Sans (OFL-1.1)

## Zubereitung

### 1 · Jedem Abschnitt des Schlussteils ein Überschriftenformat geben

Der Code steht oben in [der kurzen Antwort](https://postext.dev/de/cookbook/thesis-back-matter.md#die-kurze-antwort). `# Glossary {style="glossary" note="…"}` eröffnet einen Abschnitt, der bis zur nächsten Überschrift der Ebene 1 reicht, und seine Seiten übernehmen Layout, Kolumnentitel und Auftakt des Formats ([Überschriftenformate](https://postext.dev/de/docs/configuration-styles.md#überschriftenstile)); so wechseln Glossar und Register zu zwei Spalten, während das Literaturverzeichnis, dessen Format kein Layout festlegt, zur einen Spalte des Dokuments zurückkehrt. `numbered: false` hält diese Überschriften aus der Kapitelzählung heraus (löschen Sie es, und das Band des Glossars druckt 2), und jedes Format nennt seinen Seitenumbruch, weil eines, das keinen nennt, das `'odd'` des Kapitels erbt und leere linke Seiten hinterlässt. Die Einträge von Glossar und Literaturverzeichnis sind Absätze eines Blocks `:::paragraphs{style="…"}` in 9,3 pt auf 12,4 pt Zeilenabstand, gegenüber 11 auf 14,6 pt im Text, mit Folgezeilen, die im Glossar um 1 em und im Literaturverzeichnis um 1,5 em hängen ([Absatzformate](https://postext.dev/de/docs/configuration-styles.md#absatzformate)).

### 2 · Ein Band für jeden Auftakt zeichnen

```js
// script.js, Zeilen 55–79
const SINK = 8; // lines reserved, 41.2 mm: 3.2 mm more than the band, and text on the grid
// Design text sets each baseline 0.8 of its line under the line's top, and a line is 1.2 × the
// size unless lineHeight says otherwise. In mm, a line's part above its baseline and below it:
const PT = 25.4 / 72;
const above = (size, lineHeight = 1.2) => 0.8 * size * lineHeight * PT;
const below = (size, lineHeight = 1.2) => 0.2 * size * lineHeight * PT;
const KICKER = 4.3, TITLE = BAND - TOP - 9.5; // mm under the text block's top: two baselines
// A bottom-aligned box that ends below() under a baseline sets its last line on it. The
// numeral's line is 0.72 of its size, so the line fits inside its box.
const text = (id, content, family, size, lineHeight, baseline, edge, w, extra) => ({
  kind: 'text', id, content, fontFamily: family, fontSize: pt(size), lineHeight,
  color: col('paper'), overflow: 'wrap', align: edge.endsWith('right') ? 'right' : 'left',
  verticalAlign: 'bottom', ...extra, placement: { anchor: { to: 'container', edge },
    size: { width: mm(w), height: mm(baseline + below(size, lineHeight)) } } });
// The mark is '{number}': the chapter's 6, the appendix's A, nothing on an unnumbered heading.
const opener = (label) => ({ enabled: true, minHeight: pt(SINK * LEAD),
  slot: { elements: [
    { kind: 'box', id: 'band', style: { backgroundColor: col('band') }, placement: {
      anchor: { to: 'page', edge: 'top-left' }, size: { width: 'fill', height: mm(BAND) } } },
    text('label', label, LABEL, 8, 1.2, KICKER, 'top-left', 80,
      { fontWeight: 700, letterSpacing: pt(1.6), textTransform: 'uppercase' }),
    text('title', '{titleText}', DISPLAY, 34, 1.04, TITLE, 'top-left', 84),
    text('mark', '{number}', DISPLAY, 118, 0.72, TITLE, 'top-right', 34),
    text('note', '{attr.note}', TEXT, 8.6, 1.3, TITLE, 'top-right', 44, { italic: true }),
  ] } });
```

Das Zeichen ist `{number}`: 6 beim Kapitel, A beim Anhang und nichts bei einer nicht nummerierten Überschrift. Ein einziges Design dient allen dreien, und die Abschnitte des Schlussteils drucken ihr `{attr.note}` dort, wo die Ziffer stünde. `minHeight` reserviert acht Zeilen zu 14,6 pt, 41,2 mm ab der Oberkante des Satzspiegels, was 3,2 mm Abstand zum Band lässt und den Text auf seinem Raster hält. Designtext setzt seine Grundlinie bei 0,8 der Zeile; ein unten ausgerichteter Kasten, dessen Fuß `below()` unter einer Grundlinie sitzt, setzt also seine letzte Zeile auf diese Grundlinie. Titel, Ziffer zu 118 pt und die letzte Zeile der Notiz stehen alle auf der Grundlinie des Titels, 52,5 mm unter dem Endformat. Die Zeile der Ziffer misst 0,72 ihres Grads, sodass die Zeile in ihren Kasten passt.

### 3 · Den Anhang über sein Format mit Buchstaben zählen

```js
// script.js, Zeilen 109–115
// The style's '{1:A}' (see answer) prints the level's counter as a letter, and startAt=1 on the
// first appendix restarts it after chapter 6. The band and the running head read that letter.
// A resource template's {h1} is a number unless it names a style: '{h1:A}.{n}' counts the
// tables A.1, A.2… and B.1 in a second appendix, with one type for all of them.
const appendixHeads = heads('Appendix {chapterNumber}. {chapterTitle}');
const appendixTables = { ...defaultResourceTypes(LANG).find((type) => type.id === 'table'),
  id: 'table-a', numberingTemplate: '{h1:A}.{n}', resetOn: 'h1' }; // a copy of 'table'
```

Das Format des Anhangs setzt `numberingTemplate: '{1:A}'`, das den Zähler der Ebene als Buchstaben druckt, und `# Interview guide {style="appendix" startAt=1}` setzt diesen Zähler zurück, sodass der erste Anhang A ist und nicht G ([Überschriftenformate](https://postext.dev/de/docs/configuration-styles.md#überschriftenstile)). Das Band liest den Buchstaben über `{number}` und der Kolumnentitel über `{chapterNumber}`. `{h1}` in einem Ressourcentyp druckt den Zähler als Zahl, sofern es keinen Stil nennt; die Tabellen des Anhangs nehmen daher einen Typ, dessen Vorlage `'{h1:A}.{n}'` lautet: A.1, A.2 und so weiter, und B.1 in einem zweiten Anhang; mit dem voreingestellten Typ wäre die Tabelle Table 1.1. Die eigenen Überschriften des Kapitels zählen nach den Vorlagen der Ebenen: `'{1}.{2}'` druckt 6.1, und `continuation.headings.h1: 5` macht dieses Kapitel zum sechsten.

### 4 · Die Kolumnentitel an die Außenkante setzen

```js
// script.js, Zeilen 83–105
const HEAD = 17.5, GAP = 9; // mm: the heads' baseline under the trim; the folio to the words
// Each text is placed by its top, above() over HEAD: the folio and the capitals share a baseline.
const head = (id, content, parity, edge, x, size = 7.5, extra) => ({ kind: 'text', id, content,
  parity, pages: 'body', fontFamily: LABEL, fontSize: pt(size), fontWeight: 700,
  letterSpacing: pt(1.3), textTransform: 'uppercase', color: col('ink'), ...extra, placement: {
    anchor: { to: 'page', edge }, offset: { x: mm(x), y: mm(HEAD - above(size)) } } });
const folio = { fontFamily: TEXT, fontWeight: 400, letterSpacing: pt(0) };
const heads = (recto) => ({ elements: [
  head('verso-folio', '{pageNumber}', 'even', 'top-left', OUTER, 9.5, folio),
  head('verso', '{title}', 'even', 'top-left', OUTER + GAP),
  head('recto', recto, 'odd', 'top-right', -(OUTER + GAP)),
  head('recto-folio', '{pageNumber}', 'odd', 'top-right', -OUTER, 9.5, folio),
  // The header's container spans the text block, so one rule serves both pages.
  { kind: 'rule', id: 'hairline', pages: 'body', direction: 'horizontal', thickness: pt(0.5),
    color: col('rule'), placement: { anchor: { to: 'container', edge: 'top-left' },
      offset: { y: mm(HEAD + 2) }, size: { width: 'fill' } } },
] });
const chapterHeads = heads('Chapter {chapterNumber}. {chapterTitle}');
const sectionHeads = heads('{chapterTitle}'); // 'Glossary', 'References', 'Index'
// Openers drop the folio to the foot, centred under the text block, its baseline 12 mm below.
const footer = { elements: [{ kind: 'text', id: 'drop-folio', content: '{pageNumber}',
  pages: 'opener', ...folio, fontSize: pt(9.5), color: col('ink'), align: 'center',
  placement: { anchor: { to: 'container', edge: 'top' }, offset: { y: mm(12 - above(9.5)) } } }] };
```

Die vier Textelemente sind an der Seite verankert und nach `parity` gefiltert, was die Pagina auf beiden Seiten an der Außenkante hält; jedes wird an seiner Oberkante platziert, `above()` über einer Grundlinie 17,5 mm unter dem Endformat, sodass die Pagina zu 9,5 pt und die Versalien zu 7,5 pt auf einer Linie stehen. Die linke Seite trägt den Titel der Arbeit (`{title}` aus dem Frontmatter) und die rechte den des Abschnitts (`{chapterTitle}`), etwa „INDEX“ auf [Seite 177](https://postext.dev/cookbook/thesis-back-matter/en/p07.webp?v=6b02529f). `pages: 'body'` hält sie von den Auftaktseiten fern, deren einzige Pagina die der Fußzeile ist, zentriert unter dem Satzspiegel. Die rechte Seite des Kapitels würde *Chapter 6. Conclusion* lauten und die des Anhangs *Appendix A. Interview guide*, doch in diesem Beispiel läuft nur das Register auf eine rechte Seite weiter.

### 5 · Die Begriffe dort markieren, wo der Text sie behandelt

```js
// script.js, Zeilen 119–125
// Glossary definitions carry 'main' (bold numbers); runs of pages join as 171–72 (Chicago).
// The heads stand on the entries' 11.6 pt pitch, 7.5 pt of space above them: 19 pt from the
// last entry of a letter to the next letter's baseline, 11.6 pt from a letter to its first entry.
const index = { fontFamily: TEXT, fontSize: pt(9), lineHeight: pt(11.6), color: col('ink'),
  indent: em(1), turnoverIndent: em(2), rangeFormat: 'chicago',
  groups: { fontFamily: DISPLAY, fontSize: pt(13), fontWeight: 400, color: col('ink'),
    marginTop: pt(7.5) } };
```

`:index[tablet]` druckt sein Wort und ordnet es unter demselben Wort ein; `Rayner:index{term="Rayner, Keith"}` druckt nichts und ordnet die Seite von *Rayner* unter dem vollen Namen ein; `term="interviews!timing of"` erzeugt einen Untereintrag. Die Begriffe des Glossars tragen `main`, deshalb steht ihre Seite fett, wie die Notiz im Band des Registers sagt. `:::index` unter `# Index {style="index"}` druckt die Einträge in den zwei Spalten des Formats, und `buildDocument` setzt das Dokument erneut, bis sich die Seitenzahlen nicht mehr verschieben. Die Einträge stehen in 9 auf 11,6 pt, mit Folgezeilen, die um 2 em hängen, und um 1 em eingerückten Untereinträgen; die Buchstaben stehen in 13 pt in der Auszeichnungsschrift. Marken funktionieren nicht in Tabellenzellen, die Zeilen von Tabelle 6.1 fügen also keine Seiten hinzu: *look-backs* führt den Absatz auf, der über sie berichtet, nicht die Tabelle.

### 6 · Jede Voreinstellung in der Textfarbe halten

```js
// script.js, Zeilen 15–19
const palette = { ink: '#000000', band: '#000000', rule: '#000000', paper: '#ffffff' };
const col = (id) => ({ hex: palette[id], model: 'hex', paletteId: id });
// Bold, italic and list markers default to 'main-color': pointed at the ink, they print black.
const colorPalette = Object.entries({ ...palette, 'main-color': palette.ink })
  .map(([id, hex]) => ({ id, name: id, value: { hex, model: 'hex' } }));
```

Die Voreinstellungen der Engine für Fett, Kursiv, Listenzeichen und Verweise folgen dem Paletteneintrag `main-color`; richtet man ihn auf die Textfarbe, drucken sie schwarz statt blau ([Farbpalette](https://postext.dev/de/docs/configuration-fonts-colors-viewers.md#farbpalette)). `referenceBold: false` setzt *Table 6.1* aufrecht und normal, wie die Autor-Jahr-Zitate um ihn herum.

> Ein Tabellenverzeichnis bleibt Handarbeit. In einer ganzen Dissertation, die mit `buildBundle` Kapitel für Kapitel gesetzt wird, erhält das Kapitel mit `:::index` die Marken aller Kapitel.

## Das ganze Rezept

Eine Datei, aus dem Ordner des Rezepts zusammengesetzt, mit dem Beispieltext und dem gemeinsamen Baukasten der Rezepte bereits eingebettet; sie baut ihre eigene Seite auf. Zum Ausführen setzen Sie sie in ein `<script type="module">` auf einer leeren Seite oder fügen sie in das JS-Feld eines neuen CodePen-Pens ein (als Modul). Sie importiert postext von esm.sh, es gibt also nichts zu installieren oder zu bauen.

- Quellordner: https://github.com/drnachio/postext/tree/main/cookbook/thesis-back-matter

### script.js

```js
// ═══ Postext Cookbook · Nº 031 · Thesis back matter: appendix, glossary and index ═══
// https://postext.dev/en/cookbook/thesis-back-matter
// Code: MIT · Text: original (CC BY 4.0) · Pictures: none
// Fonts: Libertinus Serif, Serif Display and Sans (SIL OFL 1.1) · Needs postext ≥ 1.28.0
import {
  buildDocumentWithFonts, renderPageToCanvas, defaultResourceTypes,
} from 'https://esm.sh/postext';
import { renderToPdf, decompressWoff2 } from 'https://esm.sh/postext-pdf';

const LANG = 'en'; // @lang: the language of the sample document ('en')
const RECIPE = 'thesis-back-matter';

// ─── 1 · Design ─────────────────────────────────────────────────────────────
// #region palette: one ink; every colour is black or white, each under its own name
const palette = { ink: '#000000', band: '#000000', rule: '#000000', paper: '#ffffff' };
const col = (id) => ({ hex: palette[id], model: 'hex', paletteId: id });
// Bold, italic and list markers default to 'main-color': pointed at the ink, they print black.
const colorPalette = Object.entries({ ...palette, 'main-color': palette.ink })
  .map(([id, hex]) => ({ id, name: id, value: { hex, model: 'hex' } }));
// #endregion
const TEXT = 'Libertinus Serif', DISPLAY = 'Libertinus Serif Display', LABEL = 'Libertinus Sans';
const TOP = 24, INNER = 25, OUTER = 31; // mm: a 120 mm measure, about 70 characters at 11 pt
const LEAD = 14.6; // pt: the body's leading, the grid every page is set on
const BAND = 62; // mm from the trim's top: the black band at the head of every opener

// #region answer: back matter as unnumbered heading styles, entries in hanging indents
// '# Glossary {style="glossary"}' in the Markdown picks a style. Each style starts a page
// of either parity, the appendix a recto (a style that sets no break inherits its level's
// 'odd': gotcha style-inherits-break), stays out of the chapter count (numbered: false, so
// its band has no numeral) and brings its own running heads; the glossary and the index
// set their pages in two columns until the next '#'. The appendix counts, in letters: its
// template prints the level's counter as A, B… config() takes both lists below.
const twoColumns = { layoutType: 'double', gutterWidth: mm(6) };
const backMatter = (id, extra) => ({ id, numbered: false, breakBefore: { enabled: true,
  parity: 'any' }, advancedDesign: opener('Back matter'), header: sectionHeads, ...extra });
const headingStyles = () => [
  backMatter('appendix', { numbered: true, numberingTemplate: '{1:A}', header: appendixHeads,
    breakBefore: { enabled: true, parity: 'odd' }, advancedDesign: opener('Appendix') }),
  backMatter('glossary', { layout: twoColumns }),
  backMatter('references'),
  backMatter('index', { layout: twoColumns }),
];
// One paragraph per entry, in :::paragraphs{style="…"}: the turnover lines hang, so the
// first word of every entry stands clear at the left. Ragged, as APA asks of references,
// with every word whole (hyphenation.ragged is off). The index has its own settings.
const entries = (id, size, lead, hang, extra) => ({ id, fontSize: pt(size),
  lineHeight: pt(lead), textAlign: 'left', hangingIndent: em(hang), ...extra });
const paragraphStyles = () => [
  entries('term', 9.3, 12.4, 1), // the glossary: a bold term, then its definition
  entries('reference', 9.3, 12.4, 1.5, { spaceBetween: pt(2.4) }),
];
// #endregion

// #region opener: a black band across the head of the page, the title reversed out of it
const SINK = 8; // lines reserved, 41.2 mm: 3.2 mm more than the band, and text on the grid
// Design text sets each baseline 0.8 of its line under the line's top, and a line is 1.2 × the
// size unless lineHeight says otherwise. In mm, a line's part above its baseline and below it:
const PT = 25.4 / 72;
const above = (size, lineHeight = 1.2) => 0.8 * size * lineHeight * PT;
const below = (size, lineHeight = 1.2) => 0.2 * size * lineHeight * PT;
const KICKER = 4.3, TITLE = BAND - TOP - 9.5; // mm under the text block's top: two baselines
// A bottom-aligned box that ends below() under a baseline sets its last line on it. The
// numeral's line is 0.72 of its size, so the line fits inside its box.
const text = (id, content, family, size, lineHeight, baseline, edge, w, extra) => ({
  kind: 'text', id, content, fontFamily: family, fontSize: pt(size), lineHeight,
  color: col('paper'), overflow: 'wrap', align: edge.endsWith('right') ? 'right' : 'left',
  verticalAlign: 'bottom', ...extra, placement: { anchor: { to: 'container', edge },
    size: { width: mm(w), height: mm(baseline + below(size, lineHeight)) } } });
// The mark is '{number}': the chapter's 6, the appendix's A, nothing on an unnumbered heading.
const opener = (label) => ({ enabled: true, minHeight: pt(SINK * LEAD),
  slot: { elements: [
    { kind: 'box', id: 'band', style: { backgroundColor: col('band') }, placement: {
      anchor: { to: 'page', edge: 'top-left' }, size: { width: 'fill', height: mm(BAND) } } },
    text('label', label, LABEL, 8, 1.2, KICKER, 'top-left', 80,
      { fontWeight: 700, letterSpacing: pt(1.6), textTransform: 'uppercase' }),
    text('title', '{titleText}', DISPLAY, 34, 1.04, TITLE, 'top-left', 84),
    text('mark', '{number}', DISPLAY, 118, 0.72, TITLE, 'top-right', 34),
    text('note', '{attr.note}', TEXT, 8.6, 1.3, TITLE, 'top-right', 44, { italic: true }),
  ] } });
// #endregion

// #region running-heads: the thesis on the verso, the section on the recto, a hairline under
const HEAD = 17.5, GAP = 9; // mm: the heads' baseline under the trim; the folio to the words
// Each text is placed by its top, above() over HEAD: the folio and the capitals share a baseline.
const head = (id, content, parity, edge, x, size = 7.5, extra) => ({ kind: 'text', id, content,
  parity, pages: 'body', fontFamily: LABEL, fontSize: pt(size), fontWeight: 700,
  letterSpacing: pt(1.3), textTransform: 'uppercase', color: col('ink'), ...extra, placement: {
    anchor: { to: 'page', edge }, offset: { x: mm(x), y: mm(HEAD - above(size)) } } });
const folio = { fontFamily: TEXT, fontWeight: 400, letterSpacing: pt(0) };
const heads = (recto) => ({ elements: [
  head('verso-folio', '{pageNumber}', 'even', 'top-left', OUTER, 9.5, folio),
  head('verso', '{title}', 'even', 'top-left', OUTER + GAP),
  head('recto', recto, 'odd', 'top-right', -(OUTER + GAP)),
  head('recto-folio', '{pageNumber}', 'odd', 'top-right', -OUTER, 9.5, folio),
  // The header's container spans the text block, so one rule serves both pages.
  { kind: 'rule', id: 'hairline', pages: 'body', direction: 'horizontal', thickness: pt(0.5),
    color: col('rule'), placement: { anchor: { to: 'container', edge: 'top-left' },
      offset: { y: mm(HEAD + 2) }, size: { width: 'fill' } } },
] });
const chapterHeads = heads('Chapter {chapterNumber}. {chapterTitle}');
const sectionHeads = heads('{chapterTitle}'); // 'Glossary', 'References', 'Index'
// Openers drop the folio to the foot, centred under the text block, its baseline 12 mm below.
const footer = { elements: [{ kind: 'text', id: 'drop-folio', content: '{pageNumber}',
  pages: 'opener', ...folio, fontSize: pt(9.5), color: col('ink'), align: 'center',
  placement: { anchor: { to: 'container', edge: 'top' }, offset: { y: mm(12 - above(9.5)) } } }] };
// #endregion

// #region appendix: lettered by its style, '# Interview guide {style="appendix" startAt=1}'
// The style's '{1:A}' (see answer) prints the level's counter as a letter, and startAt=1 on the
// first appendix restarts it after chapter 6. The band and the running head read that letter.
// A resource template's {h1} is a number unless it names a style: '{h1:A}.{n}' counts the
// tables A.1, A.2… and B.1 in a second appendix, with one type for all of them.
const appendixHeads = heads('Appendix {chapterNumber}. {chapterTitle}');
const appendixTables = { ...defaultResourceTypes(LANG).find((type) => type.id === 'table'),
  id: 'table-a', numberingTemplate: '{h1:A}.{n}', resetOn: 'h1' }; // a copy of 'table'
// #endregion

// #region index: the pages of the :index marks, sorted under letters in the display face
// Glossary definitions carry 'main' (bold numbers); runs of pages join as 171–72 (Chicago).
// The heads stand on the entries' 11.6 pt pitch, 7.5 pt of space above them: 19 pt from the
// last entry of a letter to the next letter's baseline, 11.6 pt from a letter to its first entry.
const index = { fontFamily: TEXT, fontSize: pt(9), lineHeight: pt(11.6), color: col('ink'),
  indent: em(1), turnoverIndent: em(2), rangeFormat: 'chicago',
  groups: { fontFamily: DISPLAY, fontSize: pt(13), fontWeight: 400, color: col('ink'),
    marginTop: pt(7.5) } };
// #endregion

const config = () => ({
  locale: 'en-us', // the thesis is written in English
  colorPalette, header: chapterHeads, footer, layout: { layoutType: 'single' },
  page: { sizePreset: 'custom', width: mm(176), height: mm(250), dpi: 150, // B5
    margins: { top: mm(TOP), bottom: mm(24), left: mm(INNER), right: mm(OUTER), mirror: true } },
  bodyText: { fontFamily: TEXT, fontSize: pt(11), lineHeight: pt(LEAD), color: col('ink'),
    referenceBold: false, // 'Table 6.1' in roman, like the citations around it
    firstLineIndent: mm(4.5), indentAfterHeading: false, minWordSpacing: 0.8, maxWordSpacing: 1.8 },
  // Exact heading margins (snapToGrid: false), no lines added above them; the chapter's heads
  // measure whole grid lines.
  headings: { fontFamily: DISPLAY, fontWeight: 400, color: col('ink'), snapToGrid: false,
    balancing: { maxLinesPerHeading: 0 }, levels: [
      // Chapters open on a recto. span: 'page' (the styles inherit it) sets the band and the
      // title across both columns of the glossary and the index.
      { level: 1, numberingTemplate: '{1}', span: 'page', marginBottom: pt(0),
        advancedDesign: opener('Chapter'), breakBefore: { enabled: true, parity: 'odd' } },
      { level: 2, numberingTemplate: '{1}.{2}', fontSize: pt(14), lineHeight: pt(LEAD),
        marginTop: pt(LEAD * 1.5), marginBottom: pt(LEAD / 2) }, // three lines in all
    ] },
  headingStyles: headingStyles(), paragraphStyles: paragraphStyles(), index,
  orderedLists: { marginTop: pt(LEAD / 2), marginBottom: pt(LEAD / 2) },
  unorderedLists: { bulletChar: '–' },
  resourceTypes: [...defaultResourceTypes(LANG), appendixTables], // tables 6.1… and A.1…
  // Captions in the text face, as APA sets a table's number and title.
  captionStyle: { fontSize: pt(9), position: 'above', gap: pt(4), note: { fontSize: pt(8) } },
  // Rules only and a bold header on white: an APA table has no fills and no vertical rules.
  tableStyle: { rules: 'horizontal', borderColor: col('rule'), borderWidth: pt(0.5),
    headerBackgroundEnabled: false, headerFontSize: pt(9.5),
    bodyFontSize: pt(9.5), cellPadding: mm(1) },
  calloutStyles: [{ id: 'colophon', span: 'page', marginTop: pt(LEAD), backgroundEnabled: false,
    stripe: { enabled: true, side: 'top', width: pt(0.5), color: col('rule') },
    padding: { top: mm(2.5), right: mm(0), bottom: mm(0), left: mm(0) },
    body: { fontSize: pt(8), lineHeight: pt(10.5), firstLineIndent: pt(0), textAlign: 'left' } }],
});

// ─── 2 · Content ────────────────────────────────────────────────────────────
const markdown = String.raw`---
title: "Reading on Screens and Paper"
subtitle: "A Mixed-Methods Study of Comprehension, Confidence and Navigation"
author: "Ines Varley"
---

# Conclusion

This thesis set out to test whether it matters if a long text:index{term="texts, length of"} is read on paper or on a screen. Chapters 3 to 5 reported a within-subjects:index{term="within-subjects design"} experiment with forty-eight :index[undergraduates] and :index[interviews] with sixteen of them, combined in the :index[convergent design] described by Creswell:index{term="Creswell, John W."} and Plano Clark:index{term="Plano Clark, Vicki L."} (2018). This chapter brings the two strands together and sets out what they mean for :index[teaching] and for the :index[design of reading software].

## What the study found

:ref{id="findings" style="full"} summarises the results.:index{term="comprehension"} On :index[literal]{term="comprehension!literal"} questions, answerable from a single sentence, the medium made no difference. On :index[inferential]{term="comprehension!inferential"} questions, which required connecting ideas across paragraphs, paper readers scored higher. The difference points the same way as the :index[meta-analyses] of Delgado:index{term="Delgado, Pablo"} et al. (2018) and Clinton:index{term="Clinton, Virginia"} (2019), which found the :index[paper advantage] in :index[expository]{term="expository text"} rather than :index[narrative]{term="narrative text"} texts.

Calibration:index{term="calibration"} showed the larger difference. Screen readers:index{term="calibration!on screen"} predicted:index{term="confidence!judgements of"} higher scores than paper readers and obtained lower ones, so the gap between :index[confidence] and :index[accuracy]:index{term="calibration!bias in"} was nearly three times as wide. The result repeats the :index[overconfidence] that Ackerman:index{term="Ackerman, Rakefet"} and Goldsmith:index{term="Goldsmith, Morris"} (2011) found in students who read on screen and set their own study time.:index{term="self-regulated study"} Such readers stop once they judge a text understood, so overconfidence cuts their study short.

Paper readers also turned back:index{term="look-backs"} almost twice as often as screen readers scrolled back:index{term="scrolling"}, most often just before an inferential question.:index{term="comprehension!inferential"} In the interviews:index{term="interviews"}, eleven of the sixteen :index[participants] remembered:index{term="memory"} where on a page:index{term="spatial memory"} an idea had been (“top left, next to the diagram”), and three gave up looking for a passage on the :index[tablet] because “it could have been anywhere”. Liu:index{term="Liu, Ziming"} (2005) described a drift towards :index[browsing] and :index[keyword spotting] on screen; these readers went through the whole text but had fewer :index[landmarks] to return to.

## Implications for teaching and design

For short texts and factual questions, screens serve as well as paper.:index{term="teaching"} For long expository:index{term="expository text"} texts that students must understand:index{term="comprehension"} rather than search, paper remains the safer choice.:index{term="paper advantage"} Where it is not available, students should test their understanding instead of trusting their sense of it: in the :index[pilot sessions], a short :index[self-test] after reading halved the overconfidence:index{term="overconfidence"} on screen.:index{term="calibration!on screen"}

Readers also used the fixed position of text on a page as a map,:index{term="landmarks"} one of the uses of paper that Sellen:index{term="Sellen, Abigail J."} and Harper:index{term="Harper, Richard H. R."} (2002) observed in offices.:index{term="offices, paper in"} Reading applications:index{term="design of reading software"} that keep a stable page and show the reader’s place in the whole text may restore some of that map.

## Limitations and further work

The participants:index{term="participants"} were students at one university:index{term="university, single"} who read English fluently,:index{term="limitations"} and the medium matters more for some readers, texts and tasks than it does for others (Singer:index{term="Singer, Lauren M."} & Alexander:index{term="Alexander, Patricia A."}, 2017). The texts were expository and about 1,800 words long,:index{term="texts, length of"} and the screen condition used a single tablet.:index{term="tablet"} A :index[replication] with a larger sample, several devices and the eye-movement:index{term="eye movements"} recording reviewed by Rayner:index{term="Rayner, Keith"} (1998) would show where on the page the two media part company.

Huey:index{term="Huey, Edmund Burke"} (1908) thought that a complete analysis of what we do when we read would be almost the acme of a psychologist’s achievements. The experiments reported here add a small part to that analysis; the replication proposed above could measure how far readers rely on the position of a passage on the page when they look back.

# Interview guide {style="appendix" startAt=1}

The interviews:index{term="interviews"} took place within a week of each participant’s:index{term="participants"} second session. They were audio-recorded,:index{term="interviews!recording of"} transcribed:index{term="transcription"} in full and analysed thematically:index{term="thematic analysis"} following Braun:index{term="Braun, Virginia"} and Clarke:index{term="Clarke, Victoria"} (2006); :ref{id="session-plan" style="full"} gives their timing:index{term="interviews!timing of"}: a free recall:index{term="recall"} of the two study texts, the questions below and a short debriefing.:index{term="debriefing"} The questions were asked in this order,:index{term="interviews!questions asked"} and a prompt:index{term="interviews!prompts in"} only when the participant had not already covered its point.

1. Tell me about the last long text:index{term="texts, length of"} you read for a course.:index{term="courses, reading for"}
   - Where did you read it, and on paper or on a screen?
2. Which of the texts in this study do you remember:index{term="memory"} best, and why?
3. When you wanted to check an earlier passage, what did you do?:index{term="look-backs"}
   - How did you know where to look?
4. How sure were you of your answers?:index{term="confidence"} What made you more or less sure?
5. Did reading on the tablet:index{term="tablet"} feel different from reading on paper?
6. Some students say they read more carefully on paper. Do you?
7. What would the ideal way to read a long text for study be like?:index{term="design of reading software"}

# Glossary {style="glossary" note="Words in italics are defined under entries of their own."}

:::paragraphs{style="term"}
**calibration**:index{term="calibration" main} The agreement between a reader’s confidence in having understood a text and the accuracy of that understanding, measured here as the difference between predicted and actual scores.

**comprehension, inferential**:index{term="comprehension!inferential" main} Understanding that requires the reader to connect information from different parts of a text or to add knowledge the text does not state.

**comprehension, literal**:index{term="comprehension!literal" main} Understanding of what a single sentence or passage states directly.

**confidence judgement**:index{term="confidence!judgements of" main} A reader’s estimate, made after reading and before seeing the questions, of how many answers will be correct.

**convergent design**:index{term="convergent design" main} A mixed-methods design in which quantitative and qualitative data are collected in the same period, analysed separately and then compared.

**expository text**:index{term="expository text" main} A text written to explain or inform, such as a textbook chapter or a report, as opposed to a narrative text.

**fixation**:index{term="fixation" main} A pause of the eyes, typically about a quarter of a second, during which the reader takes in text; fixations alternate with *saccades*.

**look-back**:index{term="look-backs" main} Any return to an earlier part of a text during reading: turning back a page, scrolling up or following a link to a previous section.

**metacomprehension**:index{term="metacomprehension" main} A reader’s knowledge and monitoring of their own understanding of a text; *calibration* is one of its measures.

**navigation**:index{term="navigation" main} The movements a reader makes through a text as a whole, as distinct from the movements of the eyes along a line.

**overconfidence**:index{term="overconfidence" main} Positive *calibration* bias: predicting a higher score than the one actually obtained.

**saccade**:index{term="saccade" main} A rapid movement of the eyes from one *fixation* to the next, during which little or no text is taken in.

**screen inferiority effect**:index{term="screen inferiority effect" main} The finding that comprehension of the same text is lower on screen than on paper, most consistently for *expository texts* read under time pressure.

**self-regulated study**:index{term="self-regulated study" main} Reading in which the reader, not the experimenter, decides how long to spend on a text.

**spatial memory for text**:index{term="spatial memory" main} Memory of where on a page or in a document a piece of information appeared, used as a cue for *look-backs*.

**thematic analysis**:index{term="thematic analysis" main} A method for identifying, analysing and reporting patterns of meaning across qualitative data such as interview transcripts.

**within-subjects design**:index{term="within-subjects design" main} An experimental design in which every participant takes part in every condition, here reading on both paper and screen.
:::

# References {style="references" note="Every work cited in the thesis, set in APA style (7th edition)."}

:::paragraphs{style="reference"}
Ackerman, R., & Goldsmith, M. (2011). Metacognitive regulation of text learning: On screen versus on paper. *Journal of Experimental Psychology: Applied, 17*(1), 18–32.

Baron, N. S. (2015). *Words onscreen: The fate of reading in a digital world.* Oxford University Press.

Braun, V., & Clarke, V. (2006). Using thematic analysis in psychology. *Qualitative Research in Psychology, 3*(2), 77–101.

Clinton, V. (2019). Reading from paper compared to screens: A systematic review and meta-analysis. *Journal of Research in Reading, 42*(2), 288–325.

Creswell, J. W., & Plano Clark, V. L. (2018). *Designing and conducting mixed methods research* (3rd ed.). SAGE.

Delgado, P., Vargas, C., Ackerman, R., & Salmerón, L. (2018). Don’t throw away your printed books: A meta-analysis on the effects of reading media on reading comprehension. *Educational Research Review, 25*, 23–38.

Dillon, A. (1992). Reading from paper versus screens: A critical review of the empirical literature. *Ergonomics, 35*(10), 1297–1326.

Huey, E. B. (1908). *The psychology and pedagogy of reading.* Macmillan.

Liu, Z. (2005). Reading behavior in the digital environment: Changes in reading behavior over the past ten years. *Journal of Documentation, 61*(6), 700–712.

Mangen, A., Walgermo, B. R., & Brønnick, K. (2013). Reading linear texts on paper versus computer screen: Effects on reading comprehension. *International Journal of Educational Research, 58*, 61–68.

Noyes, J. M., & Garland, K. J. (2008). Computer- vs. paper-based tasks: Are they equivalent? *Ergonomics, 51*(9), 1352–1375.

Paterson, D. G., & Tinker, M. A. (1940). *How to make type readable.* Harper & Brothers.

Rayner, K. (1998). Eye movements in reading and information processing: 20 years of research. *Psychological Bulletin, 124*(3), 372–422.

Sellen, A. J., & Harper, R. H. R. (2002). *The myth of the paperless office.* MIT Press.

Singer, L. M., & Alexander, P. A. (2017). Reading on paper and digitally: What the past decades of empirical research reveal. *Review of Educational Research, 87*(6), 1007–1041.

Tinker, M. A. (1963). *Legibility of print.* Iowa State University Press.

Wolf, M. (2018). *Reader, come home: The reading brain in a digital world.* Harper.
:::

# Index {style="index" note="Bold numbers refer to the definitions in the glossary."}

:::index

:::callout{type="colophon"}
Set in Libertinus Serif, Libertinus Serif Display and Libertinus Sans (SIL Open Font License). Text: original, CC BY 4.0. The thesis, its author, its participants and its results are fictional; the works in the references are real.
:::
`; // content.<lang>.md, inlined by the Cookbook

// A table from rows of 'cell|cell|cell'; aligns has a letter a column, l or r.
const table = (id, typeId, caption, note, widths, aligns, rows) => ({ id, typeId, kind: 'table',
  caption, note, createdAt: 0, updatedAt: 0, table: { model: { headerRowCount: 1,
    columnWidths: widths, rows: rows.map((row, r) => row.split('|').map((content, c) => ({
      content, isHeader: r === 0, align: aligns[c] === 'r' ? 'right' : 'left' }))) } } });
const resources = [
  table('findings', 'table', 'Main results by medium',
    'Means for 48 participants. Bias is the predicted minus the actual score.', [5, 1.4, 1.4],
    'lrr', ['Measure|Paper|Screen', 'Literal comprehension (of 10)|7.8|7.7',
      'Inferential comprehension (of 10)|6.4|5.6', 'Predicted score (%)|75|78',
      'Actual score (%)|71|66.5', 'Calibration bias (points)|+4.0|+11.5',
      'Look-backs per text|5.8|3.1']),
  table('session-plan', 'table-a', 'Timing of an interview session', undefined, [1, 6, 1.4],
    'llr', ['Part|Content|Minutes', '1|Welcome, consent and a check of the recorder|3',
      '2|Free recall of the two study texts|5',
      '3|Questions 1–4: reading habits, look-backs and confidence|12',
      '4|Questions 5–7: the two media and an ideal design|12', '5|Debrief|3']),
];

// ─── 3 · Fonts ──────────────────────────────────────────────────────────────
const FONTS = { 'Libertinus Serif': ['400', '400i', '700'], 'Libertinus Serif Display': ['400'],
  'Libertinus Sans': ['700'] }; // every face the pages use, loaded first

// ─── 4 · Build & show ───────────────────────────────────────────────────────
// The thesis's sixth and last chapter opens on page 171, a recto.
const continuation = { pageIndexOffset: 170, pageNumbering: { startAt: 171 }, headings: { h1: 5 } };
// The index is laid out again until its page numbers settle, inside this one call.
const doc = await buildDocumentWithFonts({ markdown, resources, continuation }, config(),
  kitFonts(FONTS));
showPages(doc, { title: 'Reading on Screens and Paper: the back matter' });
offerPdf(() => renderToPdf(doc, { fontProvider: fontsourceProvider }), `${RECIPE}.pdf`);

// ─── Kit ── helpers shared by every Cookbook recipe · postext.dev/cookbook ─────

// ─── Kit · core v1 ── the same in every recipe · postext.dev/cookbook
function mm(value) { return { value, unit: 'mm' }; }
function pt(value) { return { value, unit: 'pt' }; }
function em(value) { return { value, unit: 'em' }; }
/** The sample language's string: t({ en: 'Figure', es: 'Figura' }). */
function t(strings) { return strings[LANG] ?? Object.values(strings)[0]; }
/** A file in this recipe's assets folder, served from the Postext repo by jsDelivr. */
function asset(file) { return `https://cdn.jsdelivr.net/gh/drnachio/postext@main/cookbook/${RECIPE}/assets/${file}`; }

// ─── Kit · fonts v3 ── the same in every recipe · postext.dev/cookbook
// Postext loads the faces a config asks for before layout (prepareFonts,
// buildDocumentWithFonts) and asks the kit for the files: Fontsource's, the
// files the PDF embeds too.

/** The options of buildDocumentWithFonts / prepareFonts / withLoadedFonts:
 *  the Fontsource resolver, and the faces of FONTS loaded with the ones the
 *  config names. FONTS = { 'Family Name': ['400', '400i', '700'] }. */
function kitFonts(FONTS = {}) {
  const faces = Object.entries(FONTS).flatMap(([family, specs]) => specs.map((spec) =>
    ({ family, weight: parseInt(spec, 10), style: spec.endsWith('i') ? 'italic' : 'normal' })));
  return { resolve: fontsourceResolver, faces };
}

/** A face's Fontsource files, in a fixed order: latin, then latin-ext and greek
 *  when `text` needs them and the family ships them (kitSubsetsFor), each with
 *  its unicode-range. Null for a weight or slant Fontsource does not ship. */
async function fontsourceResolver(family, weight, style, { text = '' } = {}) {
  const id = fontsourceId(family);
  const meta = await fontsourceMeta(family);
  if (meta && !(meta.weights.includes(weight) && meta.styles.includes(style))) return null;
  kitStatus('Loading fonts…');
  const at = await fontsourceAt(id);
  return ['latin', ...kitSubsetsFor(text, meta)].map((subset) => ({
    source: `${at}files/${id}-${subset}-${weight}-${style}.woff2`,
    unicodeRange: kitRanges()[subset],
  }));
}

/** The unicode-range of each Fontsource file the kit loads (a function: the
 *  kit is inlined after the pen's code, which runs before a const would be set). */
function kitRanges() {
  return {
    latin: 'U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,'
      + 'U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD',
    'latin-ext': 'U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,'
      + 'U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF',
    greek: 'U+0370-03FF',
  };
}

/** For the cjk, arabic and comics blocks, which add their own files: the
 *  faces of `faces` = { 'Family Name': ['400', '400i', '700'] }. `text` is the sample:
 *  č ł † α χ also load latin-ext and greek files (kitSubsetsFor). With
 *  `optional`, a face Fontsource does not ship is skipped instead of failing.
 *  The files are added once all have loaded, in the order asked (latin, then
 *  latin-ext, then greek), whatever order they arrive in: the last one added
 *  wins a character two files hold. Resolves to the number of faces added. */
async function loadFonts(faces, text = '', { optional = false } = {}) {
  kitStatus('Loading fonts…');
  const ranges = kitRanges();
  const jobs = [];
  for (const [family, specs] of Object.entries(faces)) {
    const id = fontsourceId(family);
    const todo = [...new Set(specs)].map((spec) => [parseInt(spec, 10), spec.endsWith('i') ? 'italic' : 'normal'])
      .filter(([weight, style]) => !hasFace(family, weight, style)); // before any await
    const at = fontsourceAt(id);
    const meta = optional || /[^\0-ÿ]/u.test(text) ? await fontsourceMeta(family) : null;
    const subsets = ['latin', ...kitSubsetsFor(text, meta)];
    for (const [weight, style] of todo) {
      if (optional && !(meta?.weights.includes(weight) && meta.styles.includes(style))) continue;
      for (const subset of subsets) {
        const url = `${await at}files/${id}-${subset}-${weight}-${style}.woff2`;
        const face = new FontFace(family, `url(${url}) format('woff2')`,
          { weight: String(weight), style, unicodeRange: ranges[subset] });
        jobs.push(face.load().catch(() => {
          if (subset === 'latin' && !optional) throw new Error(`Fontsource has no ${family} ${weight} ${style}`);
        }));
      }
    }
  }
  const ready = await Promise.all(jobs).catch((error) => { kitFail(error); throw error; });
  return ready.filter((face) => face && document.fonts.add(face)).length;
}

/** A loaded FontFace covers this family, weight and style (fonts.check() would
 *  also say yes for families nobody declared). */
function hasFace(family, weight, style) {
  for (const face of document.fonts) {
    if (face.status !== 'loaded' || face.style !== style) continue;
    if (face.family.replace(/^["']|["']$/g, '') !== family) continue;
    const [low, high = low] = face.weight.split(' ').map(Number);
    if (weight >= low && weight <= high) return true;
  }
  return false;
}

/** The files beyond latin `text` needs that `meta`'s family ships. */
function kitSubsetsFor(text, meta) {
  return [[/[Ā-˿ᴀ-ᶿḀ-ỿ†ℓⱠ-Ɀ꜠-ꟿ]/u, 'latin-ext'], [/[Ͱ-Ͽ]/u, 'greek']]
    .filter(([re, x]) => re.test(text) && meta?.subsets?.includes(x)).map(([, x]) => x);
}

/** Fontsource's id for a family: 'Source Serif 4' → 'source-serif-4'. */
function fontsourceId(family) { return family.toLowerCase().replace(/\s+/g, '-'); }

/** A package's folder at the one version @5 names now (#766). */
function fontsourceAt(id, u = `https://cdn.jsdelivr.net/npm/@fontsource/${id}@`) {
  return (fontsourceAt.c ??= {})[id] ??= fetch(u + '5/package.json').then((r) => r.json())
    .then((p) => p.version || 5, () => 5).then((v) => `${u}${v}/`);
}

/** The family's Fontsource metadata (weights, styles, subsets), or null. */
function fontsourceMeta(family) {
  fontsourceMeta.cache ??= new Map();
  const id = fontsourceId(family);
  if (!fontsourceMeta.cache.has(id)) {
    fontsourceMeta.cache.set(id, fetch(`https://api.fontsource.org/v1/fonts/${id}`)
      .then((res) => (res.ok ? res.json() : null), () => null));
  }
  return fontsourceMeta.cache.get(id);
}

// ─── Kit · viewer v2 ── the same in every recipe · postext.dev/cookbook
/** The pages as spreads on a dark desk, page 1 alone, then verso | recto,
 *  each painted when it scrolls near. Other options go to renderPageToCanvas. */
function showPages(docs, { title, width = 460, ...render } = {}) {
  const root = viewer(title);
  const pages = [docs].flat().flatMap((doc) =>
    doc.pages.map((page) => ({ doc, page, n: (doc.pageIndexOffset ?? 0) + page.index })));
  const spreads = [];
  let verso = null;
  for (const p of pages) {
    if (p.n % 2 === 1) { if (verso) spreads.push([verso, null]); verso = p; }
    else { spreads.push([verso, p]); verso = null; }
  }
  if (verso) spreads.push([verso, null]);
  const density = Math.min(window.devicePixelRatio || 1, 2);
  showPages.painter?.disconnect();
  const painter = new IntersectionObserver((entries) => {
    for (const { isIntersecting, target } of entries) {
      if (!isIntersecting) continue;
      painter.unobserve(target);
      const { doc, page } = target.postext;
      renderPageToCanvas(page, doc, target, { ...render, scale: (width * density) / page.width });
    }
  }, { rootMargin: '800px' });
  showPages.painter = painter;
  root.replaceChildren(...spreads.map((pair) => {
    const spread = document.createElement('div');
    spread.className = 'pt-spread';
    for (const p of pair) {
      const figure = document.createElement('figure');
      if (p) {
        const label = p.page.pageLabel || String(p.n + 1);
        const canvas = document.createElement('canvas');
        canvas.postext = p;
        canvas.style.aspectRatio = `${p.page.width} / ${p.page.height}`;
        canvas.setAttribute('role', 'img');
        canvas.setAttribute('aria-label', `Page ${label}`);
        const folio = document.createElement('figcaption');
        folio.textContent = label;
        figure.append(canvas, folio);
        painter.observe(canvas);
      } else figure.className = 'pt-blank';
      spread.append(figure);
    }
    return spread;
  }));
  kitStatus(`${pages.length} ${pages.length === 1 ? 'page' : 'pages'}`);
  document.documentElement.dataset.postext = 'ready';
  return pages.length;
}

/** The desk, the bar and the error reporting, created once. */
function viewer(title) {
  if (!document.getElementById('pt-kit')) {
    document.head.insertAdjacentHTML('beforeend', `<style id="pt-kit">
      :root { color-scheme: dark; }
      body { margin: 0; background: #0e1014; color: #b9bcc4; font: 13px/1.45 system-ui, sans-serif; }
      #pt-bar { position: sticky; top: 0; z-index: 1; display: flex; flex-wrap: wrap; align-items: center;
        gap: 6px 16px; padding: 10px 16px; background: rgb(14 16 20 / .92); backdrop-filter: blur(6px);
        border-bottom: 1px solid #23262d; }
      #pt-bar strong { color: #f4f1ea; font-weight: 600; }
      #pt-actions { display: flex; gap: 12px; margin-left: auto; }
      #pt-actions a, #pt-actions button { color: #d8a21a; font: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
      #pages { display: grid; justify-items: center; gap: 48px; padding: 32px 16px 72px; }
      .pt-spread { display: flex; }
      .pt-spread figure { margin: 0; width: min(460px, 44vw); }
      .pt-spread canvas { display: block; width: 100%; background: #fff;
        box-shadow: 0 1px 2px rgb(0 0 0 / .5), 0 22px 44px -16px rgb(0 0 0 / .8); }
      .pt-spread figure:first-child canvas { box-shadow: inset -14px 0 14px -14px rgb(0 0 0 / .18), 0 1px 2px rgb(0 0 0 / .5), 0 22px 44px -16px rgb(0 0 0 / .8); }
      .pt-spread figcaption { margin-top: 10px; text-align: center; font: 600 10px/1 system-ui, sans-serif;
        letter-spacing: .18em; text-transform: uppercase; color: #6c7079; }
      .pt-blank { visibility: hidden; }
      @media (max-width: 760px) {
        .pt-spread { flex-direction: column; gap: 32px; }
        .pt-spread figure { width: min(460px, 92vw); }
        .pt-blank { display: none; }
      }
    </style>`);
    document.body.insertAdjacentHTML('afterbegin',
      '<header id="pt-bar"><strong id="pt-title"></strong><span id="pt-status" role="status"></span><span id="pt-actions"></span></header>');
    document.getElementById('pt-title').textContent = document.title || 'Postext';
    addEventListener('error', (event) => kitFail(event.error ?? event.message));
    addEventListener('unhandledrejection', (event) => kitFail(event.reason));
  }
  if (title) document.getElementById('pt-title').textContent = title;
  return document.getElementById('pages')
    ?? document.body.appendChild(Object.assign(document.createElement('main'), { id: 'pages' }));
}

function kitStatus(text) {
  viewer();
  document.getElementById('pt-status').textContent = text;
}

function kitFail(error) {
  document.documentElement.dataset.postext = 'error';
  kitStatus(`Error: ${error?.message ?? error}`);
}

// ─── Kit · pdf v3 ── the same in every recipe that exports a PDF
/** The Fontsource files the screen used, as TrueType, with their range and
 *  place: the nearest weight the family ships, upright with no italic;
 *  latin, then what its letters need (kitSubsetsFor). */
async function fontsourceProvider(family, weight, style, request) {
  const id = fontsourceId(family);
  const meta = await fontsourceMeta(family);
  const weights = meta?.weights?.length ? meta.weights : [400, 700];
  const w = weights.reduce((a, b) => (Math.abs(b - weight) < Math.abs(a - weight) ? b : a));
  const s = style === 'italic' && meta && !meta.styles.includes('italic') ? 'normal' : style;
  const text = String.fromCodePoint(...(request?.codePoints ?? []));
  const files = await Promise.all(['latin', ...kitSubsetsFor(text, meta)].map(async (subset, order) => {
    const res = await fetch(`${await fontsourceAt(id)}files/${id}-${subset}-${w}-${s}.woff2`);
    if (!res.ok) throw new Error(`Fontsource has no ${family} ${w} ${s} ${subset}`);
    return Object.assign(await decompressWoff2(new Uint8Array(await res.arrayBuffer())),
      { unicodeRange: kitRanges()[subset], order });
  }));
  return files.length === 1 ? files[0] : files;
}

/** A "Build the PDF" button; then "Open the PDF" (a new tab: CodePen's frame
 *  shows no PDFs) and a download link. */
function offerPdf(makePdf, filename) {
  viewer();
  const button = Object.assign(document.createElement('button'), { type: 'button', textContent: 'Build the PDF' });
  button.dataset.postextPdf = filename;
  button.addEventListener('click', async () => {
    button.disabled = true;
    button.textContent = 'Building the PDF…';
    try {
      const bytes = await makePdf();
      const url = URL.createObjectURL(new Blob([bytes], { type: 'application/pdf' }));
      const size = `${Math.max(1, Math.round(bytes.length / 1024))} KB`;
      button.replaceWith(
        Object.assign(document.createElement('a'), { href: url, target: '_blank', rel: 'noopener', textContent: 'Open the PDF ↗' }),
        Object.assign(document.createElement('a'), { href: url, download: filename, textContent: `Download ${filename} · ${size}` }));
    } catch (error) {
      button.disabled = false;
      button.textContent = 'Build the PDF';
      kitFail(error);
    }
  });
  document.getElementById('pt-actions').append(button);
}

// ─── /Kit ───────────────────────────────────────────────────────────────────────
```

## Varianten

### Jeden Abschnitt auf einer rechten Seite beginnen

Dissertationen, die für eine Bibliothek gebunden werden, beginnen jeden Abschnitt oft auf einer rechten Seite. Glossar, Literaturverzeichnis und Register rücken dann auf die Seiten 175, 177 und 179, jeweils nach einer leeren linken Seite, und die fetten Zahlen des Registers verweisen auf Seite 175.

```diff
-const backMatter = (id, extra) => ({ id, numbered: false, breakBefore: { enabled: true,
-  parity: 'any' }, advancedDesign: opener('Back matter'), header: sectionHeads, ...extra });
+const backMatter = (id, extra) => ({ id, numbered: false, breakBefore: { enabled: true,
+  parity: 'odd' }, advancedDesign: opener('Back matter'), header: sectionHeads, ...extra });
```

### Auch den Anfang der Dissertation setzen

Die Titelei, vor Seite 1 in römischen Ziffern gezählt, behandelt [Titelei mit römischen Paginas, dann Seite 1](https://postext.dev/de/cookbook/front-matter-roman-to-arabic.md).

## Häufige Fehler

- **Ein Überschriftenformat erbt den Seitenumbruch seiner Ebene.** Ein Eintrag in headingStyles übernimmt jedes Feld, das er auslässt, von seiner Überschriftenebene, breakBefore eingeschlossen. Ein Inhaltsverzeichnis oder Impressum, das nach einem :::pagebreak über eine H1 gestaltet wird, erbt die Parität 'odd' und landet hinter einer Vakatseite. Geben Sie einem solchen Format breakBefore: { enabled: false }.
- **Seite 1 ist eine rechte Seite: Seiten mit physischen Nummern planen.** Seite 1 ist eine rechte Seite und Seite 2 die erste linke, planen Sie Doppelseiten also mit physischen Seitenzahlen: ein Auftakt auf einer geraden Seite steht der ungeraden Seite danach gegenüber.

## Nachweise

- Rezept: Ignacio Ferro ([@drnachio](https://github.com/drnachio))
- Schrift: Libertinus Serif (OFL-1.1), Libertinus Serif Display (OFL-1.1), Libertinus Sans (OFL-1.1)
- Code: MIT · Beispielinhalt: CC-BY-4.0

## Verwandte Rezepte

- [Nº 006 · Titelei mit römischen Paginas, dann Seite 1](https://postext.dev/de/cookbook/front-matter-roman-to-arabic.md): Umschlag und Titelei sind nicht nummerierte Überschriften, römisch in Kleinbuchstaben gezählt; :::numbering beginnt rechts beim Roman wieder bei 1. · Stufe 3 (Fortgeschritten) · Belletristik, Drama und Prosa
- [Nº 020 · Endnoten auf einer eigenen Seite, in zwei Spalten](https://postext.dev/de/cookbook/endnotes-instead-of-footnotes.md): Ein kurzer Präprozessor nummeriert die Noten [^1] und sammelt sie unter einer Überschrift „Notes“, deren Format sie auf eine eigene, zweispaltige Seite setzt. · Stufe 2 (Mittel) · Fachartikel und Wissenschaft
- [Nº 002 · Zweispaltiger Aufsatz mit nummerierten Gleichungen](https://postext.dev/de/cookbook/journal-article-with-maths.md): Ein zweispaltiger Physikaufsatz, dessen Formeln im Text und sieben nummerierte Gleichungen MathJax setzt und die im PDF als Vektoren bleiben. · Stufe 3 (Fortgeschritten) · Fachartikel und Wissenschaft
