# Zeitschriftentitel und Inhaltsverzeichnis nach Rubriken

> Ein Fototitel mit Titelzeilen unter dem Logo und ein erzeugtes Inhaltsverzeichnis mit farbiger Zeile je Rubrik. Jede Rubrik ist ein Teil ohne eigene Seite.

- HTML-Fassung: https://postext.dev/de/cookbook/magazine-cover-and-contents
- Rezept Nº 023 · Vollständige Publikationen · Stufe 3 (Fortgeschritten) · Ausgaben: Canvas
- Gattungen: Magazine und Zines
- Benötigt postext ≥ 1.25.0 · getestet mit 1.31.0 am 2026-10-13
- Seiten: [1](https://postext.dev/cookbook/magazine-cover-and-contents/en/p01.webp?v=50a1d0da), [2](https://postext.dev/cookbook/magazine-cover-and-contents/en/p02.webp?v=50a1d0da), [3](https://postext.dev/cookbook/magazine-cover-and-contents/en/p03.webp?v=50a1d0da), [4](https://postext.dev/cookbook/magazine-cover-and-contents/en/p04.webp?v=50a1d0da), [5](https://postext.dev/cookbook/magazine-cover-and-contents/en/p05.webp?v=50a1d0da), [6](https://postext.dev/cookbook/magazine-cover-and-contents/en/p06.webp?v=50a1d0da)
- In der Sandbox öffnen: https://postext.dev/de/sandbox#recipe=magazine-cover-and-contents&lang=en (.postext: https://postext.dev/cookbook/magazine-cover-and-contents/en/magazine-cover-and-contents.postext)
- Zuletzt aktualisiert: 2026-10-10
- Andere Sprachen: [en](https://postext.dev/en/cookbook/magazine-cover-and-contents.md), [es](https://postext.dev/es/cookbook/magazine-cover-and-contents.md), [ca](https://postext.dev/ca/cookbook/magazine-cover-and-contents.md), [fr](https://postext.dev/fr/cookbook/magazine-cover-and-contents.md), [it](https://postext.dev/it/cookbook/magazine-cover-and-contents.md), [pt](https://postext.dev/pt/cookbook/magazine-cover-and-contents.md), [zh](https://postext.dev/zh/cookbook/magazine-cover-and-contents.md), [ja](https://postext.dev/ja/cookbook/magazine-cover-and-contents.md), [ar](https://postext.dev/ar/cookbook/magazine-cover-and-contents.md)

## Kurz gesagt

Titel und erste Seiten einer Zeitschrift über Essen und Landwirtschaft, mit Fototitel und Inhaltsseite. Es zeigt, wie jede Rubrik ihre eigene Farbe bekommt, die sich durch ihre Seiten zieht.

## Was Sie setzen

Sechs Seiten aus einer Ausgabe von *FALLOW*, einer erfundenen Vierteljahresschrift über Essen und Land, auf einer Seite von 230 × 300 mm. Ein toskanisches Tal läuft randabfallend über den Titel unter einem Logo in der Bodoni zu 118 pt, und drei Titelzeilen stehen auf einer papierfarbenen Fläche, jede Dachzeile in der Farbe ihrer Rubrik, mit Preis und Strichcode in der Ecke. Seite 2 enthält das erzeugte Inhaltsverzeichnis: ein farbiger Reiter je Rubrik, darunter Titel, Seitenzahl und Vorspann jedes Beitrags. Die rechte Spalte trägt das Editorial, die Mitwirkenden und das Impressum. Die folgenden Rubriken sind terrakotta, oliv und weinrot. Jeder Beitrag beginnt mit einem Bild, das randabfallend über den Kopf der Seite läuft, und einer Fläche in der Rubrikfarbe, die hineinragt, und seine Seitenzahlreiter, Aufzählungszeichen und Kästen nehmen diese Farbe an. Die Farbe setzt der Direktivenblock `:::part` über jedem Beitrag, der keine Trennseite eröffnet.

**Dieses Rezept beantwortet:**

- Wie gestalte ich Titelseite und Impressum einer Zeitschrift, und was ersetzt Schmutztitel und Titelseite?
- Wie füge ich ein Inhaltsverzeichnis hinzu, das sich selbst aktualisiert (Führungspunkte, Seitenzahlen, Autoren, Teilzeilen)?
- Wie gliedere ich ein Buch in Teile oder Abschnitte, jeweils mit eigener Farbe und Teiltitelseite?
- Wie gebe ich jedem Kapitel ein eigenes Foto, eine eigene Farbe oder eine eigene Auftaktvariante?

## Die kurze Antwort

```js
// script.js, Zeilen 39–74
const MASTHEAD = TRIM.width - MARGIN.inner - MARGIN.outer; // the word spans the text block
const PANEL = 84; // mm: the cover lines' panel, a column of paper over the photograph
const SEAM = 0.3; // mm: each box overlaps the one above, so no hairline of photo shows
// A cover line: a kicker in its section's colour over a line in Bodoni, on paper boxes of one
// width. Chained with 'below' they read as one panel, and a longer line pushes the rest down.
const sheet = (top, bottom) => ({ backgroundColor: col('paper'),
  padding: { top: mm(top), right: mm(5), bottom: mm(bottom), left: mm(5) } });
const small = { ...label, fontSize: pt(8), letterSpacing: pt(1.6), color: col('ink') };
const coverLine = (n, colour, above = n === 1 ? '#masthead' : `#line${n - 1}`) => [
  text(`kicker${n}`, `{attr.kicker${n}}`, { ...small, color: col(colour),
    box: sheet(n === 1 ? 5 : 4, 1.5) },
  { ...at(above, 'below', 0, n === 1 ? 8 : -SEAM), size: { width: mm(PANEL) } }),
  text(`line${n}`, `{attr.line${n}}`, { ...bodoni, italic: true, fontSize: pt(15),
    lineHeight: 1.12, color: col('ink'), box: sheet(0, n === 3 ? 5 : 0) }, // line 3 ends the panel
  { ...at(`#kicker${n}`, 'below', 0, -SEAM), size: { width: mm(PANEL) } }),
];
const cover = { enabled: true, slot: { elements: [
  { kind: 'image', id: 'photo', resourceId: 'cover', // the JPEG is cropped to the trim, 23 : 30
    placement: { ...at('bleed', 'top-left'), size: { width: 'fill', height: 'fill' } } },
  text('strap', '{subtitle}', small, at('page', 'top-left', MARGIN.inner, 12)), // a recto
  text('issue', '{attr.issue}', { ...small, align: 'right' },
    at('page', 'top-right', -MARGIN.outer, 12)),
  text('masthead', '{titleText}', { ...bodoni, fontWeight: 900, fontSize: pt(118),
    lineHeight: 0.86, // set tight: the masthead is capitals, with no descenders
    letterSpacing: pt(1), textTransform: 'uppercase', align: 'center', color: col('ink') },
  { ...at('#strap', 'below', 0, 6), size: { width: mm(MASTHEAD) } }),
  ...coverLine(1, 'olive'), ...coverLine(2, 'terracotta'), ...coverLine(3, 'wine'),
  // The price and a made-up barcode sit in the corner, anchored to the page, not the words.
  text('price', '{attr.price}', { ...small, fontSize: pt(7), box: sheet(1, 1.5) },
    { ...at('page', 'bottom-right', -MARGIN.outer, -12), size: { width: mm(32) } }),
  { kind: 'image', id: 'barcode', resourceId: 'barcode',
    placement: { ...at('#price', 'above', 0, SEAM), size: { width: mm(32) } } },
] } };
// hook-up: headingStyles gets { id: 'cover', numbered: false, toc: false, advancedDesign: cover,
// footer: { elements: [] } }, with no folio; the Markdown's first line is
// # Fallow {style="cover" issue="…" price="…" kicker1="…" line1="…" … kicker3="…" line3="…"}
```

## Zutaten

**Zeigt**

- [Gestaltungselemente verankern](https://postext.dev/de/docs/configuration-page-layout.md#platzierung-von-elementen): Elemente am Container, an der Seite, am Anschnitt oder an einem anderen Element ausrichten (right-of, below, align-*) statt über Koordinaten.
- [Inhaltsverzeichnis](https://postext.dev/de/docs/configuration-notes-references.md#inhaltsverzeichnis): Ein :::toc, aus den Überschriften aufgebaut: Füllpunkte, Seitenbezeichnungen wie gedruckt, Autorenzeilen, farbige Teilzeilen und anklickbare Einträge im PDF.
- [Farben je Teil](https://postext.dev/de/docs/configuration-styles.md#der-container-part): Ein Teil oder ein Überschriftenformat überschreibt Einträge der Palette, sodass Bänder, Kolumnentitel und Textakzente die Farbe des Abschnitts annehmen.

**Verwendet außerdem**

- [Kolumnentitel je Abschnitt](https://postext.dev/de/docs/configuration-styles.md#überschriftenstile)
- [Gestaltete Auftakte](https://postext.dev/de/docs/configuration-text.md#seitenbreite-und-erweiterte-gestaltung)
- [Überschriftenformate](https://postext.dev/de/docs/configuration-styles.md#überschriftenstile)
- [Kapitel ohne Nummer](https://postext.dev/de/docs/configuration-styles.md#überschriftenstile)
- [Bilder in Seitengestaltungen](https://postext.dev/de/docs/configuration-page-layout.md#bildelemente)
- [Text, Linien und Kästen in Seitengestaltungen](https://postext.dev/de/docs/configuration-page-layout.md#kopf--und-fußzeilen)
- [Überschriftenattribute](https://postext.dev/de/docs/document-format.md#attribute-von-überschriften)
- [Semantische Farbpalette](https://postext.dev/de/docs/configuration-fonts-colors-viewers.md#farbpalette)
- [Kapitelband über die ganze Breite](https://postext.dev/de/docs/configuration-text.md#seitenbreite-und-erweiterte-gestaltung)
- [Platzierung von Abbildungen](https://postext.dev/de/docs/document-format.md#platzierung)
- [Gleitende Kästen](https://postext.dev/de/docs/configuration-styles.md#der-container-callout)
- [Kästen](https://postext.dev/de/docs/configuration-styles.md#kastenstile)
- [Absatzformate](https://postext.dev/de/docs/configuration-styles.md#absatzformate)
- [Eigene Ressourcentypen](https://postext.dev/de/docs/configuration-resources.md#ressourcentypen)
- [Abbildungen und Tabellen als Ressourcen](https://postext.dev/de/docs/document-format.md#ressourcen)
- [Seiten auf einem Canvas](https://postext.dev/de/docs/configuration-programmatic-usage.md#eine-seite-als-bitmap-rendern)
- [Abbildungen genau hier](https://postext.dev/de/docs/document-format.md#blockeinbettung-optional-ausdrückliche-platzierung-im-text)
- [Papierfarbe](https://postext.dev/de/docs/configuration-page-layout.md#seite)
- [Seiten- und Spaltenumbrüche](https://postext.dev/de/docs/document-format.md#pagebreak)
- [Teiltitelseiten](https://postext.dev/de/docs/configuration-styles.md#der-container-part)
- [Expliziter vertikaler Abstand](https://postext.dev/de/docs/document-format.md#space)

**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), [`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), [`parts`](https://postext.dev/de/docs/configuration-styles.md#teile), [`resourceTypes`](https://postext.dev/de/docs/configuration-resources.md#ressourcentypen), [`toc`](https://postext.dev/de/docs/configuration-notes-references.md#inhaltsverzeichnis), [`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), [`registerResourceImage`](https://postext.dev/de/docs/architecture.md#api-oberfläche), [`renderPageToCanvas`](https://postext.dev/de/docs/configuration-programmatic-usage.md#eine-seite-als-bitmap-rendern)

**Schriften**

- Spectral (OFL-1.1), Bodoni Moda (OFL-1.1), Jost (OFL-1.1)

## Zubereitung

### 1 · Den Titel am Logo aufhängen

Der Code steht oben in [der kurzen Antwort](https://postext.dev/de/cookbook/magazine-cover-and-contents.md#die-kurze-antwort). Nur das Foto, die beiden Zeilen am Kopf und der Preis sind an der Seite verankert. Alles andere ist an einem anderen Element verankert ([Elementplatzierung](https://postext.dev/de/docs/configuration-page-layout.md#platzierung-von-elementen)): das Logo unter der Oberzeile, die erste Dachzeile 8 mm unter dem Logo, dann jede Zeile unter ihrer Dachzeile und jede Dachzeile unter der Zeile davor, sodass eine längere Titelzeile oder ein größeres Logo das Darunterstehende nach unten schiebt, statt darüber zu drucken. Jede Dachzeile und jede Zeile trägt ihren eigenen papierfarbenen Kasten, 84 mm breit, und die sechs übereinandergestapelten Kästen bilden die Fläche, die mit den Wörtern wächst. Jeder Kasten nach dem ersten überlappt den darüber um 0,3 mm (`SEAM`), und der Strichcode überlappt den Preis auf dieselbe Weise, weil auf dem Canvas zwei Füllungen, die sich nur berühren, eine Haarlinie des Fotos zwischen sich lassen. Eine Zeitschrift hat weder Schmutztitel noch Titelseite; der Titel trägt ihren Namen, und die Inhaltsseite trägt Heftnummer, Jahreszeit und Impressum. Für die Titelei eines Buches siehe [Titelei mit römischen Paginas, dann Seite 1](https://postext.dev/de/cookbook/front-matter-roman-to-arabic.md).

### 2 · Jede Rubrik zu einem Teil ohne Seite machen

```js
// script.js, Zeilen 78–87
const parts = { page: false }; // a :::part now only sets the section's title and palette
const story = (id, picture) => ({ id, numbered: false, advancedDesign: opener(picture) });
const headingStyles = () => [
  { id: 'cover', numbered: false, toc: false, advancedDesign: cover,
    footer: { elements: [] } }, // no folio on the cover (the header is empty everywhere)
  { id: 'contents', numbered: false, toc: false, advancedDesign: contentsOpener,
    footer: folioLine('{title}', '{publishDate}') }, // no section yet: the issue instead
  // One opener for every story; the style names its picture, the part its colour.
  story('grain', 'flasks'), story('rest', 'fields'), story('bread', 'harvest'),
];
```

Mit `parts.page: false` eröffnet ein Direktivenblock wie `:::part{title="Features" palette="band=#5d6a2b"}` keine Trennseite und setzt nur Titel und Palette der Rubrik ab dem nächsten Block ([Der Container `:::part`](https://postext.dev/de/docs/configuration-styles.md#der-container-part)). Die H1 jedes Beitrags nimmt ein [Überschriftenformat](https://postext.dev/de/docs/configuration-styles.md#überschriftenstile) mit `numbered: false`, sodass das Inhaltsverzeichnis sie ohne Nummer aufführt. Das Format wählt das Bild des Auftakts, und der Teil liefert die Farbe.

### 3 · Jede Rubrikfarbe mit einem Eintrag verknüpfen

```js
// script.js, Zeilen 14–27
const palette = {
  ink: '#1c1a16', // text: a warm near-black
  paper: '#fbf8f1', // the page, and type set on colour
  band: '#8b6a3e', // the house earth; each :::part fence brings its own 'band'
  muted: '#6b6358', // credits and the standfirsts in the contents
  olive: '#5d6a2b', terracotta: '#a3472a', wine: '#7a2c3a', // as in the :::part fences: edit both
};
// A part overrides by id: col() writes the id beside the hex, and the designs follow it.
const col = (id) => ({ hex: palette[id], model: 'hex', paletteId: id });
const colorPalette = [
  ...Object.entries(palette).map(([id, hex]) => ({ id, name: id, value: { hex, model: 'hex' } })),
  // The engine's defaults link to 'main-color': point it at the ink, so nothing prints blue.
  { id: 'main-color', name: 'ink (defaults)', value: { hex: palette.ink, model: 'hex' } },
];
```

Ein Teil überschreibt Paletteneinträge über ihre ID, deshalb verweist alles, was sich mit der Rubrik ändert, auf `band`: die Fläche des Auftakts, der Seitenzahlreiter, die Aufzählungszeichen und Listennummern, das Zitat und Streifen und Titel des Faktenkastens. Im Text geht die Überschreibung nach Wert: Jede Farbe, deren Hexwert dem Grund-Hexwert von `band` (`#8b6a3e`) gleicht, nimmt die der Rubrik an, weshalb kein anderer Eintrag diesen Hexwert verwendet. Gestaltungselemente funktionieren umgekehrt: Die Überschreibung eines Teils erreicht sie über die `paletteId`, deshalb schreibt `col()` die ID neben den Hexwert.

### 4 · Das Inhaltsverzeichnis sich selbst gruppieren lassen

```js
// script.js, Zeilen 91–124
const mid = at('container', 'left', 0, -1); // the row's middle, 1 mm up: air above the title
const contents = { // passed to the config as `toc`
  levels: [{ level: 1, fontFamily: 'Bodoni Moda', fontSize: pt(17), fontWeight: 600,
    marginBottom: pt(2 * LEAD) }], // in ink, on the body's 13.5 pt leading
  pageNumber: { fontSize: pt(26), width: mm(14) }, // in the levels' Bodoni
  leader: { enabled: false }, subtitle: { enabled: true, attr: 'standfirst', // in italic
    fontFamily: 'Spectral', fontSize: pt(10), color: col('muted') },
  // A part row is a design as wide as the column; its 'band' takes the part's palette. No
  // {pageNumber} in it: each story under the tab prints its own page.
  parts: { height: pt(2 * LEAD), design: { elements: [ // two grid lines (the default: 2 em)
    { kind: 'rule', id: 'line', thickness: pt(0.75), color: col('band'), // behind the tab
      placement: { ...mid, size: { width: 'fill' } } },
    text('tab', '{titleText}', { ...label, fontSize: pt(8), letterSpacing: pt(1.6),
      color: col('paper'), box: { backgroundColor: col('band'),
        padding: { top: mm(1.2), right: mm(2.4), bottom: mm(1.2), left: mm(2.4) } } },
    mid),
  ] } },
};
// The colophon closes the contents page; the Markdown calls it with :::paragraphs.
const colophon = { id: 'colophon', fontFamily: 'Jost', fontSize: pt(7), lineHeight: pt(10),
  color: col('muted'), textAlign: 'left', firstLineIndent: pt(0), marginTop: pt(LEAD) };
const air = { padding: { bottom: mm(9) } }; // empty padding counts: the columns start lower
const contentsOpener = { enabled: true, slot: { elements: [
  { kind: 'rule', id: 'rule', thickness: pt(1), color: col('ink'),
    placement: { ...at('container', 'top-left'), size: { width: 'fill' } } },
  text('kicker', '{titleText}', { ...label, fontSize: pt(9), letterSpacing: pt(1.8),
    color: col('band') }, at('#rule', 'below', 0, 5)),
  text('issue', '{attr.issue}', { ...bodoni, italic: true, fontSize: pt(44), lineHeight: 1.05,
    color: col('ink') }, at('#kicker', 'below', 0, 2)),
  text('strap', '{subtitle}', { fontFamily: 'Spectral', italic: true, fontSize: pt(11),
    color: col('muted'), align: 'left' }, at('#issue', 'below', 0, 1.5)),
  text('number', '{attr.number}', { ...bodoni, fontWeight: 900, fontSize: pt(160), lineHeight: 1,
    align: 'right', color: col('band'), box: air }, at('container', 'top-right', 0, 4)),
] } };
```

`:::toc` listet die Beiträge und gibt jedem `:::part` eine Zeile, angelegt aus `toc.parts.design` mit der Palette dieses Teils ([Inhaltsverzeichnis](https://postext.dev/de/docs/configuration-notes-references.md#inhaltsverzeichnis)), sodass eine einzige Zeilengestaltung drei Reiter in drei Farben druckt. Linie und Reiter hängen beide an `mid`, der um 1 mm angehobenen Mitte der Zeile, was den Reiter vom Titel darunter freihält. Ohne Füllzeichen läuft nichts zwischen einem Titel und seiner Seitenzahl (3, 4 und 6), und `subtitle.attr` druckt das Attribut `standfirst` des Auftakts kursiv unter den Titel. Das Impressum am Ende der rechten Spalte ist ein Absatzformat, Jost 7 pt in `muted`, das das Markdown mit `:::paragraphs{style="colophon"}` anwendet.

### 5 · Jedem Beitrag denselben Auftakt geben

```js
// script.js, Zeilen 128–152
const PHOTO = 150; // mm from the top edge to the foot of the picture
const FOOT = PHOTO - MARGIN.top; // the same foot from the container, which starts at the margin
const LIFT = 34; // mm: how far the panel rises into the picture
const WIDE = 136; // mm: the panel's width
const panel = (top, bottom) => ({ backgroundColor: col('band'),
  padding: { top: mm(top), right: mm(6), bottom: mm(bottom), left: mm(6) } });
const below = (id) => ({ ...at(`#${id}`, 'below', 0, -SEAM), size: { width: mm(WIDE) } });
const opener = (resourceId) => ({ enabled: true, slot: { elements: [
  { kind: 'image', id: 'picture', resourceId, // fitted, never cropped: each file is 230 : 150
    placement: { ...at('bleed', 'top-left'), size: { width: 'fill', height: mm(PHOTO) } } },
  text('credit', '{attr.credit}', { ...small, fontSize: pt(6.5), color: col('muted'),
    letterSpacing: pt(0.6), align: 'right' }, at('container', 'top-right', 0, FOOT + 2)),
  text('section', '{partTitle}', { ...label, fontSize: pt(8.5), letterSpacing: pt(1.7),
    color: col('paper'), box: panel(6, 2) }, { ...at('container', 'top-left', 0, FOOT - LIFT),
    size: { width: mm(WIDE) } }),
  text('title', '{titleText}', { ...bodoni, fontWeight: 700, fontSize: pt(32), lineHeight: 1.02,
    color: col('paper'), box: panel(0, 3) }, below('section')),
  text('standfirst', '{attr.standfirst}', { fontFamily: 'Spectral', italic: true,
    fontSize: pt(11.5), lineHeight: 1.35, overflow: 'wrap', align: 'left',
    color: col('paper'), box: panel(0, 6) }, below('title')),
  // The panel and the byline reach below the picture's foot, so they set where the story
  // starts; the byline's empty padding keeps the first line 5 mm under it.
  text('byline', '{attr.byline}', { ...small, fontSize: pt(7.5), letterSpacing: pt(1.3),
    box: { padding: { top: mm(4), bottom: mm(5) } } }, at('#standfirst', 'below')),
] } });
```

Das Bild läuft oben randabfallend aus der Seite und reicht 150 mm hinunter. Eine Fläche in `band` ragt 34 mm in seinen Fuß. Die Fläche besteht aus drei Textkästen von 136 mm Breite, jeder mit `below` an den vorigen gekettet, mit dem Namen der Rubrik aus `{partTitle}`, dem Titel und dem Vorspann. Fläche und Autorenzeile reichen unter den Fuß des Bildes, deshalb beginnt der Beitrag unter dem tiefsten Element, der Autorenzeile, deren leerer unterer Innenabstand von 5 mm die erste Zeile mindestens so weit darunter hält.

### 6 · Die Rubrik in der Pagina nennen

```js
// script.js, Zeilen 156–171
const TAB = 6.5; // mm: the tab's side; the words beside it take its height and centre on it
const folioLine = (first, second) => ({ elements: [['even', 'left', 1], ['odd', 'right', -1]]
  .flatMap(([parity, side, s]) => {
    const inward = s > 0 ? 'right-of' : 'left-of'; // from the outer edge towards the gutter
    const beside = (id, content, style, to, gap) => text(id, content, { ...style, align: side },
      { ...at(to, inward, s * gap), size: { height: mm(TAB) } }); // text centres vertically
    return [
      text(`tab-${parity}`, '{pageNumber}', { ...label, fontSize: pt(8), color: col('paper'),
        align: 'center', box: { backgroundColor: col('band') } },
      { ...at('page', `bottom-${side}`, s * MARGIN.outer, -11),
        size: { width: mm(TAB), height: mm(TAB) } }),
      beside(`first-${parity}`, first, { ...small, color: col('band') }, `#tab-${parity}`, 3),
      beside(`second-${parity}`, second, { fontFamily: 'Spectral', italic: true,
        fontSize: pt(8), color: col('muted') }, `#first-${parity}`, 2.5),
    ].map((element) => ({ ...element, parity })); // no pages filter: openers get folios too
  }) });
```

Die Seitenzahl steht in einem Quadrat aus `band` von 6,5 mm am äußeren Fuß. Der Name der Rubrik ist daneben verankert, zum Bund hin, und der Titel des Beitrags neben dem Namen; alle drei Kästen sind 6,5 mm hoch, ihr Text vertikal zentriert, sodass sie eine Mittellinie teilen. Zeitschriften drucken die Pagina auch auf einer Auftaktseite, deshalb hat die Fußzeile keinen Filter `pages`. Das Überschriftenformat des Titels leert die Fußzeile, und das Format des Inhaltsverzeichnisses setzt den Namen der Zeitschrift und die Jahreszeit der Ausgabe (`{title}` und `{publishDate}`) dorthin, wo Rubrik und Beitrag stehen.

## 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/magazine-cover-and-contents

### script.js

```js
// ═══ Postext Cookbook · Nº 023 · Magazine cover and sectioned contents ═══════════════
// https://postext.dev/en/cookbook/magazine-cover-and-contents
// Code: MIT · Text: original (CC BY 4.0) · Photos: R. Heuvel, chuttersnap, m. tuna (CC0), diffusion
// Fonts: Spectral, Bodoni Moda, Jost (SIL OFL 1.1) · Needs postext ≥ 1.25.0
import {
  buildDocumentWithFonts, renderPageToCanvas, registerResourceImage,
} from 'https://esm.sh/postext';

const LANG = 'en'; // @lang: the language of the sample document ('en' | 'es')
const RECIPE = 'magazine-cover-and-contents';

// ─── 1 · Design ─────────────────────────────────────────────────────────────
// #region palette: one house colour, 'band', that every section overrides
const palette = {
  ink: '#1c1a16', // text: a warm near-black
  paper: '#fbf8f1', // the page, and type set on colour
  band: '#8b6a3e', // the house earth; each :::part fence brings its own 'band'
  muted: '#6b6358', // credits and the standfirsts in the contents
  olive: '#5d6a2b', terracotta: '#a3472a', wine: '#7a2c3a', // as in the :::part fences: edit both
};
// A part overrides by id: col() writes the id beside the hex, and the designs follow it.
const col = (id) => ({ hex: palette[id], model: 'hex', paletteId: id });
const colorPalette = [
  ...Object.entries(palette).map(([id, hex]) => ({ id, name: id, value: { hex, model: 'hex' } })),
  // The engine's defaults link to 'main-color': point it at the ink, so nothing prints blue.
  { id: 'main-color', name: 'ink (defaults)', value: { hex: palette.ink, model: 'hex' } },
];
// #endregion
const TRIM = { width: 230, height: 300 }; // an independent-magazine trim, in mm
const MARGIN = { top: 22, bottom: 20, inner: 16, outer: 14 }; // mirrored
const LEAD = 13.5; // body leading in pt: the baseline grid
const caps = { fontFamily: 'Jost', fontWeight: 600, textTransform: 'uppercase' }; // the label face
const label = { ...caps, align: 'left' }; // design text is centred by default
const bodoni = { fontFamily: 'Bodoni Moda', overflow: 'wrap', align: 'left' };
const at = (to, edge, x = 0, y = 0) => ({ anchor: { to, edge }, offset: { x: mm(x), y: mm(y) } });
const text = (id, content, look, placement) => ({ kind: 'text', id, content, placement, ...look });

// #region answer: the cover, one design slot whose words hang from each other
const MASTHEAD = TRIM.width - MARGIN.inner - MARGIN.outer; // the word spans the text block
const PANEL = 84; // mm: the cover lines' panel, a column of paper over the photograph
const SEAM = 0.3; // mm: each box overlaps the one above, so no hairline of photo shows
// A cover line: a kicker in its section's colour over a line in Bodoni, on paper boxes of one
// width. Chained with 'below' they read as one panel, and a longer line pushes the rest down.
const sheet = (top, bottom) => ({ backgroundColor: col('paper'),
  padding: { top: mm(top), right: mm(5), bottom: mm(bottom), left: mm(5) } });
const small = { ...label, fontSize: pt(8), letterSpacing: pt(1.6), color: col('ink') };
const coverLine = (n, colour, above = n === 1 ? '#masthead' : `#line${n - 1}`) => [
  text(`kicker${n}`, `{attr.kicker${n}}`, { ...small, color: col(colour),
    box: sheet(n === 1 ? 5 : 4, 1.5) },
  { ...at(above, 'below', 0, n === 1 ? 8 : -SEAM), size: { width: mm(PANEL) } }),
  text(`line${n}`, `{attr.line${n}}`, { ...bodoni, italic: true, fontSize: pt(15),
    lineHeight: 1.12, color: col('ink'), box: sheet(0, n === 3 ? 5 : 0) }, // line 3 ends the panel
  { ...at(`#kicker${n}`, 'below', 0, -SEAM), size: { width: mm(PANEL) } }),
];
const cover = { enabled: true, slot: { elements: [
  { kind: 'image', id: 'photo', resourceId: 'cover', // the JPEG is cropped to the trim, 23 : 30
    placement: { ...at('bleed', 'top-left'), size: { width: 'fill', height: 'fill' } } },
  text('strap', '{subtitle}', small, at('page', 'top-left', MARGIN.inner, 12)), // a recto
  text('issue', '{attr.issue}', { ...small, align: 'right' },
    at('page', 'top-right', -MARGIN.outer, 12)),
  text('masthead', '{titleText}', { ...bodoni, fontWeight: 900, fontSize: pt(118),
    lineHeight: 0.86, // set tight: the masthead is capitals, with no descenders
    letterSpacing: pt(1), textTransform: 'uppercase', align: 'center', color: col('ink') },
  { ...at('#strap', 'below', 0, 6), size: { width: mm(MASTHEAD) } }),
  ...coverLine(1, 'olive'), ...coverLine(2, 'terracotta'), ...coverLine(3, 'wine'),
  // The price and a made-up barcode sit in the corner, anchored to the page, not the words.
  text('price', '{attr.price}', { ...small, fontSize: pt(7), box: sheet(1, 1.5) },
    { ...at('page', 'bottom-right', -MARGIN.outer, -12), size: { width: mm(32) } }),
  { kind: 'image', id: 'barcode', resourceId: 'barcode',
    placement: { ...at('#price', 'above', 0, SEAM), size: { width: mm(32) } } },
] } };
// hook-up: headingStyles gets { id: 'cover', numbered: false, toc: false, advancedDesign: cover,
// footer: { elements: [] } }, with no folio; the Markdown's first line is
// # Fallow {style="cover" issue="…" price="…" kicker1="…" line1="…" … kicker3="…" line3="…"}
// #endregion

// #region sections: parts with no divider page, and a heading style for each kind of page
const parts = { page: false }; // a :::part now only sets the section's title and palette
const story = (id, picture) => ({ id, numbered: false, advancedDesign: opener(picture) });
const headingStyles = () => [
  { id: 'cover', numbered: false, toc: false, advancedDesign: cover,
    footer: { elements: [] } }, // no folio on the cover (the header is empty everywhere)
  { id: 'contents', numbered: false, toc: false, advancedDesign: contentsOpener,
    footer: folioLine('{title}', '{publishDate}') }, // no section yet: the issue instead
  // One opener for every story; the style names its picture, the part its colour.
  story('grain', 'flasks'), story('rest', 'fields'), story('bread', 'harvest'),
];
// #endregion

// #region contents: generated from the headings, with a row in each section's colour
const mid = at('container', 'left', 0, -1); // the row's middle, 1 mm up: air above the title
const contents = { // passed to the config as `toc`
  levels: [{ level: 1, fontFamily: 'Bodoni Moda', fontSize: pt(17), fontWeight: 600,
    marginBottom: pt(2 * LEAD) }], // in ink, on the body's 13.5 pt leading
  pageNumber: { fontSize: pt(26), width: mm(14) }, // in the levels' Bodoni
  leader: { enabled: false }, subtitle: { enabled: true, attr: 'standfirst', // in italic
    fontFamily: 'Spectral', fontSize: pt(10), color: col('muted') },
  // A part row is a design as wide as the column; its 'band' takes the part's palette. No
  // {pageNumber} in it: each story under the tab prints its own page.
  parts: { height: pt(2 * LEAD), design: { elements: [ // two grid lines (the default: 2 em)
    { kind: 'rule', id: 'line', thickness: pt(0.75), color: col('band'), // behind the tab
      placement: { ...mid, size: { width: 'fill' } } },
    text('tab', '{titleText}', { ...label, fontSize: pt(8), letterSpacing: pt(1.6),
      color: col('paper'), box: { backgroundColor: col('band'),
        padding: { top: mm(1.2), right: mm(2.4), bottom: mm(1.2), left: mm(2.4) } } },
    mid),
  ] } },
};
// The colophon closes the contents page; the Markdown calls it with :::paragraphs.
const colophon = { id: 'colophon', fontFamily: 'Jost', fontSize: pt(7), lineHeight: pt(10),
  color: col('muted'), textAlign: 'left', firstLineIndent: pt(0), marginTop: pt(LEAD) };
const air = { padding: { bottom: mm(9) } }; // empty padding counts: the columns start lower
const contentsOpener = { enabled: true, slot: { elements: [
  { kind: 'rule', id: 'rule', thickness: pt(1), color: col('ink'),
    placement: { ...at('container', 'top-left'), size: { width: 'fill' } } },
  text('kicker', '{titleText}', { ...label, fontSize: pt(9), letterSpacing: pt(1.8),
    color: col('band') }, at('#rule', 'below', 0, 5)),
  text('issue', '{attr.issue}', { ...bodoni, italic: true, fontSize: pt(44), lineHeight: 1.05,
    color: col('ink') }, at('#kicker', 'below', 0, 2)),
  text('strap', '{subtitle}', { fontFamily: 'Spectral', italic: true, fontSize: pt(11),
    color: col('muted'), align: 'left' }, at('#issue', 'below', 0, 1.5)),
  text('number', '{attr.number}', { ...bodoni, fontWeight: 900, fontSize: pt(160), lineHeight: 1,
    align: 'right', color: col('band'), box: air }, at('container', 'top-right', 0, 4)),
] } };
// #endregion

// #region opener: one story opener; its colour comes from the part, not from the design
const PHOTO = 150; // mm from the top edge to the foot of the picture
const FOOT = PHOTO - MARGIN.top; // the same foot from the container, which starts at the margin
const LIFT = 34; // mm: how far the panel rises into the picture
const WIDE = 136; // mm: the panel's width
const panel = (top, bottom) => ({ backgroundColor: col('band'),
  padding: { top: mm(top), right: mm(6), bottom: mm(bottom), left: mm(6) } });
const below = (id) => ({ ...at(`#${id}`, 'below', 0, -SEAM), size: { width: mm(WIDE) } });
const opener = (resourceId) => ({ enabled: true, slot: { elements: [
  { kind: 'image', id: 'picture', resourceId, // fitted, never cropped: each file is 230 : 150
    placement: { ...at('bleed', 'top-left'), size: { width: 'fill', height: mm(PHOTO) } } },
  text('credit', '{attr.credit}', { ...small, fontSize: pt(6.5), color: col('muted'),
    letterSpacing: pt(0.6), align: 'right' }, at('container', 'top-right', 0, FOOT + 2)),
  text('section', '{partTitle}', { ...label, fontSize: pt(8.5), letterSpacing: pt(1.7),
    color: col('paper'), box: panel(6, 2) }, { ...at('container', 'top-left', 0, FOOT - LIFT),
    size: { width: mm(WIDE) } }),
  text('title', '{titleText}', { ...bodoni, fontWeight: 700, fontSize: pt(32), lineHeight: 1.02,
    color: col('paper'), box: panel(0, 3) }, below('section')),
  text('standfirst', '{attr.standfirst}', { fontFamily: 'Spectral', italic: true,
    fontSize: pt(11.5), lineHeight: 1.35, overflow: 'wrap', align: 'left',
    color: col('paper'), box: panel(0, 6) }, below('title')),
  // The panel and the byline reach below the picture's foot, so they set where the story
  // starts; the byline's empty padding keeps the first line 5 mm under it.
  text('byline', '{attr.byline}', { ...small, fontSize: pt(7.5), letterSpacing: pt(1.3),
    box: { padding: { top: mm(4), bottom: mm(5) } } }, at('#standfirst', 'below')),
] } });
// #endregion

// #region folios: a folio tab in the section's colour, then the section and the story
const TAB = 6.5; // mm: the tab's side; the words beside it take its height and centre on it
const folioLine = (first, second) => ({ elements: [['even', 'left', 1], ['odd', 'right', -1]]
  .flatMap(([parity, side, s]) => {
    const inward = s > 0 ? 'right-of' : 'left-of'; // from the outer edge towards the gutter
    const beside = (id, content, style, to, gap) => text(id, content, { ...style, align: side },
      { ...at(to, inward, s * gap), size: { height: mm(TAB) } }); // text centres vertically
    return [
      text(`tab-${parity}`, '{pageNumber}', { ...label, fontSize: pt(8), color: col('paper'),
        align: 'center', box: { backgroundColor: col('band') } },
      { ...at('page', `bottom-${side}`, s * MARGIN.outer, -11),
        size: { width: mm(TAB), height: mm(TAB) } }),
      beside(`first-${parity}`, first, { ...small, color: col('band') }, `#tab-${parity}`, 3),
      beside(`second-${parity}`, second, { fontFamily: 'Spectral', italic: true,
        fontSize: pt(8), color: col('muted') }, `#first-${parity}`, 2.5),
    ].map((element) => ({ ...element, parity })); // no pages filter: openers get folios too
  }) });
// #endregion

const flush = { marginTop: pt(0), marginBottom: pt(0) }; // lists sit on the grid
const marker = { fontFamily: 'Jost', fontWeight: 600, color: col('band'), ...flush }; // list marks
const boxText = { textAlign: 'left', hyphenation: false, firstLineIndent: pt(0) };
const boxStyle = { backgroundEnabled: false, ...flush, // one device: a stripe in 'band'
  stripe: { enabled: true, side: 'top', width: pt(2), color: col('band') },
  padding: { top: mm(3), right: pt(0), bottom: pt(0), left: pt(0) } };
const config = () => ({
  locale: t({ en: 'en-us', es: 'es' }), // each edition's hyphenation patterns
  colorPalette, resourceTypes: [photoType],
  page: { width: mm(TRIM.width), height: mm(TRIM.height), dpi: 150,
    backgroundColor: col('paper'), margins: { top: mm(MARGIN.top), bottom: mm(MARGIN.bottom),
      left: mm(MARGIN.inner), right: mm(MARGIN.outer), mirror: true } }, // left = inner
  layout: { gutterWidth: mm(6) }, // two columns, the default
  bodyText: { fontFamily: 'Spectral', fontSize: pt(9.5), lineHeight: pt(LEAD), // justified
    color: col('ink'), boldColor: col('band'), italicColor: col('ink'),
    referenceColor: col('ink'), firstLineIndent: mm(3.5), indentAfterHeading: false,
    minWordSpacing: 0.7, maxWordSpacing: 1.7, // tighter than the 0.6–2 defaults
    runtMinCharacters: 40 }, // counted in word spaces: last lines under about 20 letters cost
  headings: { fontFamily: 'Bodoni Moda', marginBottom: pt(0), levels: [
    // 'any': a story opens on the very next page, left or right.
    { level: 1, span: 'page', breakBefore: { enabled: true, parity: 'any' } },
    { level: 2, fontSize: pt(15), lineHeight: pt(LEAD), fontWeight: 400, italic: true,
      color: col('band'), marginTop: pt(LEAD) },
  ] },
  headingStyles: headingStyles(), parts, toc: contents,
  unorderedLists: { ...marker, bulletChar: '–' }, orderedLists: marker, // in the label face
  // The quote keeps a line above and a line under it; the floated box has neither.
  calloutStyles: [{ id: 'quote', ...boxStyle, marginTop: pt(LEAD), marginBottom: pt(LEAD),
    body: { fontFamily: 'Bodoni Moda', fontSize: pt(16), lineHeight: pt(1.5 * LEAD),
      italicColor: col('band'), ...boxText } },
  { id: 'facts', ...boxStyle, placement: 'top', // floats to the next column head
    titleStyle: { ...caps, fontSize: pt(7.5), letterSpacing: pt(1.5), color: col('band') },
    body: { fontFamily: 'Jost', fontSize: pt(8.5), lineHeight: pt(12), ...boxText } }],
  captionStyle: { fontFamily: 'Jost', fontSize: pt(7.5), gap: mm(2),
    note: { fontSize: pt(6.5), color: col('muted') } },
  paragraphStyles: [{ id: 'bios', firstLineIndent: pt(0), spaceBetween: pt(LEAD / 2) }, colophon],
  header: { elements: [] }, footer: folioLine('{partTitle}', '{chapterTitle}'), // at the foot
});

// ─── 2 · Content ────────────────────────────────────────────────────────────
const photoType = { id: 'photo', // no figure number: an empty caption prefix and template
  name: t({ en: 'Photograph', es: 'Fotografía' }), shortLabel: t({ en: 'photo', es: 'foto' }),
  captionPrefix: '', numberingTemplate: '', resetOn: 'never', counterFormat: 'decimal' };
const picture = (id, fileId, [width, height], alt, extra = {}) => { // alt: { en, es }
  const kind = fileId.endsWith('.svg') ? 'svg' : 'bitmap';
  return { id, kind, typeId: 'photo', createdAt: 0, updatedAt: 0, altText: t(alt), ...extra,
    [kind]: { fileId, width, height, ...(kind === 'bitmap' && { format: 'jpeg' }) } };
};
const resources = [ // alt texts and captions in both languages; t() picks the edition's
  picture('cover', 'castellina-cover-1150.jpg', [1150, 1500], { en: 'Vines, cypresses and a '
    + 'farmhouse below hazy hills.', es: 'Viñas, cipreses y una casa bajo colinas brumosas.' }),
  picture('thumb', 'castellina-cover-1150.jpg', [1150, 1500], { en: 'The cover of this issue.',
    es: 'La portada de este número.' }, { placement: { position: 'here', width: 0.45 },
    caption: t({ en: 'On the cover: the hills below Castellina in Chianti, where our feature '
      + 'is set.', es: 'En la portada: las colinas de Castellina in Chianti, escenario del '
      + 'reportaje.' }),
    note: t({ en: 'Photograph: Rowan Heuvel, CC0', es: 'Fotografía: Rowan Heuvel, CC0' }) }),
  picture('flasks', 'flasks-1920.jpg', [1920, 1252], { en: 'Glass flasks holding green shoots '
    + 'on a shelf.', es: 'Matraces de vidrio con brotes verdes en una balda.' }),
  picture('fields', 'fields-1610.jpg', [1610, 1050], { en: 'Fields from above, one '
    + 'unsown, and a farmhouse.', es: 'Campos desde arriba, uno sin sembrar, y una casa.' }),
  picture('harvest', 'harvest-1840.jpg', [1840, 1200], { en: 'Ripe wheat, a combine harvester '
    + 'blurred behind it.', es: 'Trigo maduro y, desenfocada detrás, una cosechadora.' }),
  picture('barcode', 'barcode.svg', [320, 140], { en: 'A barcode.', es: 'Un código de barras.' }),
  // 1530 px at 150 dpi would print 259 mm: it shrinks to the measure (gotcha: bitmap-print-size).
  // A top float opens the page after its ::resource line (gotcha: top-float-next-page).
  picture('vines', 'castellina-farm-1530.jpg', [1530, 900], { en: 'Rows of vines, cypresses and '
    + 'a farmhouse on a Tuscan slope.', es: 'Hileras de viñas, cipreses y una casa de campo en '
    + 'una ladera toscana.' }, { placement: { position: 'top', span: 'page' },
    caption: t({ en: 'Vines and cypresses by a farmhouse near Castellina in Chianti, in summer.',
      es: 'Viñas y cipreses junto a una casa de campo de Castellina in Chianti, en verano.' }),
    note: t({ en: 'Photograph: Rowan Heuvel, CC0, via Wikimedia Commons',
      es: 'Fotografía: Rowan Heuvel, CC0, vía Wikimedia Commons' }) }),
];

const markdown = String.raw`---
title: "Fallow"
subtitle: "A quarterly of food and land"
publishDate: "Autumn 2026"
---

# Fallow {style="cover" issue="No. 14 · Autumn 2026" price="£12 · No. 14" kicker1="Features" line1="The year of rest: on a Chianti farm, every field gets a year off" kicker2="Field notes" line2="The grain archive: two hundred old wheats, kept alive in a village school" kicker3="Kitchen" line3="Threshing-day bread, and a loaf for the last sheaf"}

# Contents {style="contents" number="14" issue="Autumn 2026"}

:::toc

::resource{id="thumb"}

:::columnbreak

## From the editor

A field left unsown for a year has an image problem. To a passing driver it looks abandoned; to an accountant it is a line with nothing in it. The farmers in this issue use another word for it. They say the field is resting, the word a baker uses for a dough left under a cloth to rise.

Our feature follows one farm above Castellina in Chianti through its seven-year rotation, including the year in which a field grows only weeds and the farmer gets a letter from the council about the thistles. In Field notes, a seed library in a former village school keeps two hundred old wheats alive by sowing a few of them every autumn. And in the Kitchen, the loaf baked on the day the last sheaf comes in, with a little of that old grain in the dough.

We called this magazine *Fallow* fourteen issues ago, half as a joke. A quarterly is slow to make, and there were seasons when the name read more like a warning. We kept it because the pieces we are proudest of took a year or more, and for most of that year nothing seemed to happen.

*Clara Ashdown, editor*

## Contributors

:::paragraphs{style="bios"}
**Tomás Arrieta** writes about seeds and the people who save them. He lives in Poggibonsi, where he keeps bees, badly.

**Lucia Fenn** grew up on a dairy farm in Somerset and has reported on Italian farming for twelve years.

**Nell Harrow** bakes and teaches in Siena. Her book on country breads comes out next spring.
:::

:::paragraphs{style="colophon"}
FALLOW is an imaginary quarterly of food and land, made for the Postext Cookbook. Set in Spectral, Bodoni Moda and Jost (SIL Open Font License). Text: CC BY 4.0. Photographs: Rowan Heuvel, chuttersnap and meriç tuna, CC0, via Wikimedia Commons; the fields are a generated image and the barcode is drawn in code.
:::

:::part{title="Field notes" palette="band=#a3472a"}
:::

# The Grain Archive {style="grain" standfirst="In a former village school in the Val d’Elsa, a seed library keeps two hundred old wheats alive by sowing a few of them every autumn and putting the new grain back in the jars." byline="Words by Tomás Arrieta" credit="Photograph: chuttersnap, CC0"}

The classroom still has its blackboard. On it, someone has chalked the week’s sowing list: Verna, Gentil Rosso, Frassineto and a wheat with no name at all, only a village and a year. Behind the desks, where the coats used to hang, stand the shelves: two hundred jars of grain, each sealed with wax and labelled by hand.

The library began in 2009, when a retired teacher found a sack of seed in her late uncle’s barn and could not bring herself to feed it to the hens. Today it has forty members with borrowing cards, and a rule that whoever takes a jar in autumn brings back twice as much grain the following summer.

On the Saturday we visited, the borrowers came in one by one with their sacks. A baker from Colle took two jars of Verna for a field he rents behind the bypass. A woman who grows beans for the market wanted a wheat short enough to stand up to the wind on her hilltop, and left with one from the Casentino that nobody had sown since 1987. Each loan went into a ledger in pencil: the jar, the name, the field, and a line left blank for the harvest.

The rule is there because seed ages. In a cool, dry jar a wheat keeps its vigour for a few years; after that, fewer and fewer grains sprout, until one spring none do. National gene banks slow the clock by drying their seed and storing it at eighteen degrees below zero. A village library has no freezer that size, so it sows its wheats, a few rows at a time, and puts fresh seed back on the shelf.

In the back room, under a bank of lamps, a row of glass flasks holds the hardest cases: grains so old that the librarians no longer trust them to a field. Most will not sprout. The few that grow are planted out in March in the test plot behind the school, where children from the new school down the road come in June to count the ears.

Every jar has a card saying who grew the wheat, on which slope, and what it was good for. Some of the notes are older than the library. One, in a spidery hand on a ration book, says only: good for straw, poor for bread, Grandfather swore by it.

Nobody here claims that the old wheats are better. They grow tall, so a storm flattens them, and they yield less than modern varieties. But some stand up to a dry spring or a late frost, and last year a plant breeder from Florence borrowed three of them for her drought trials.

:::part{title="Features" palette="band=#5d6a2b"}
:::

# The Year of Rest {style="rest" standfirst="On a farm above Castellina in Chianti, seven fields take turns at wheat, beans, sulla and barley. Every year one of them is left unsown, and the wheat that follows needs little fertiliser." byline="Words by Lucia Fenn" credit="Photograph: generated for FALLOW"}

Marco Rinaldi keeps a map of his farm on the kitchen wall, drawn on the back of a feed merchant’s calendar. It shows seven fields, numbered in pencil, and beside each a column of crops running back to 1998, when he took over from his father. Read across, the columns make a staircase: wheat, field beans, wheat again, two years of sulla for the sheep, a year of barley, and then a year in which the square is left blank.

“That one,” he says, tapping this year’s blank, “is the field everybody asks about.”

It is field four, twelve hectares of clay on the slope below the house. In May it was ploughed once, shallowly, and since then nobody has touched it. By late August, when we walked it, it had grown a shin-high crop of whatever wanted to grow: wild oats, poppies gone to seed, chicory with its blue flowers shut against the heat, and the thistles that make the neighbours shake their heads.

::resource{id="vines"}

Leaving land unsown is one of the oldest practices in farming, and before artificial fertiliser most farmers could not do without it. Medieval villages across northern Europe worked their open fields in three: one sown in autumn, one in spring, one left to rest. Here the old word for it is *maggese*, from *maggio*, because the resting field was ploughed in May, and in the hills around Siena farmers kept the habit long after the tractors came.

The rest works slowly, and most of what it does happens underground. A year without a crop breaks the life cycle of the pests and diseases that follow wheat from field to field. The weeds that come up are grazed or ploughed in before they set seed, and their roots open the clay. The beans and the sulla of the years before have already stored nitrogen in the soil, and the fallow gives it time to become something the next wheat can use.

It also costs money, which is why most farms gave it up. A field that grows nothing earns nothing, and a farm that must pay for its tractor every month finds it hard to leave a seventh of its land alone. Rinaldi did the sums as everyone does. “On paper I lose a seventh of the farm every year,” he says. “On paper.”

:::callout{type="facts" title="Seven fields, seven years"}
Every autumn each field moves one step down the list, so one of the seven is always resting.

1. Wheat
2. Field beans
3. Wheat
4. Sulla, grazed by sheep
5. Sulla, grazed by sheep
6. Barley
7. Fallow
:::

The paper leaves out the next wheat. On the fields that have rested, his yields in the following year come close to those of neighbours who spread fertiliser every spring, and his bill for fertiliser and sprays is a fraction of theirs. In the wet autumn of 2023, when the valley’s wheat went in late and patchy, field six, the year after its rest, came up so evenly that a neighbour stopped to look at it.

A fallow field does look untidy, and in a landscape sold on postcards, untidiness is noticed. Rinaldi has had a letter from the council about his thistles and a visit from a man selling weedkiller who thought that the farm had been abandoned. “I tell them it is on holiday,” he says. “They don’t laugh.”

A young couple farming below Radda have begun a five-field rotation of their own, and the seed library in the Val d’Elsa now sends its members a sheet on how to rest a plot. In June, the resting field was the only one on the slope where skylarks nested, and the barn owls from the ruin up the hill hunt it every evening, because the voles have moved in.

:::callout{type="quote"}
*“On paper I lose a seventh of the farm every year. On paper.”*
:::

Towards the end of our visit, Rinaldi took us back into field four. Near the hedge the soil had cracked in the heat, and he knelt and broke off a clod. Inside, it was dark and crumbly and threaded with roots, and a worm withdrew from the light. “Look at that,” he said, the way another farmer might show you a prize heifer. “That is a year’s work.”

In October he will plough the field again and sow it with wheat, and next summer the square on the calendar will have a crop in it. Another field will take its turn to rest. By then, he says, the neighbours will have found something else to talk about.

:::part{title="Kitchen" palette="band=#7a2c3a"}
:::

# Threshing-Day Bread {style="bread" standfirst="When the last load of grain came in, farm kitchens baked with the new flour. This loaf keeps a fifth of an old wheat in the dough." byline="Recipe by Nell Harrow" credit="Photograph: meriç tuna, CC0"}

All over Europe, the end of the harvest had its rites. The last sheaf was plaited into a doll, drenched with water or carried home on the last cart, and the kitchen baked with grain from the new crop. This loaf takes its cue from those, with a fifth of the flour milled from an old Tuscan wheat such as Verna. The old flour is weaker, so the dough is wetter than it looks; fold it instead of adding flour. It makes two loaves, one to keep and one to give away.

## Ingredients

- 800 g strong white bread flour
- 200 g stoneground flour from an old wheat
- 700 g water, lukewarm
- 200 g active sourdough starter
- 20 g fine sea salt

:::columnbreak

## Method

1. Mix both flours with 650 g of the water and leave for an hour.
2. Add the starter, the salt and the rest of the water, and squeeze them in with a wet hand until the dough is smooth.
3. For the next two hours, fold the dough over itself every half hour. Then leave it covered until it has risen by half, three to four hours in a warm kitchen.
4. Divide the dough, shape each half into a tight round and set it seam up in a floured basket. Cover and chill overnight.
5. Heat the oven to 250 °C with a lidded cast-iron pot inside. Tip in one loaf, slash the top and bake for 20 minutes with the lid on, then 20 to 25 minutes more without it at 230 °C, until deep brown. Bake the second loaf the same way.
6. Leave to cool for an hour before cutting. On threshing day, the first slice goes to whoever brought in the last sheaf.

:::space

If you have no old wheat, use any stoneground wholemeal flour. The loaf comes out a shade darker, with a closer crumb.
`; // content.<lang>.md, inlined by the Cookbook

// #region art: a made-up barcode, drawn in code with a seeded PRNG
function mulberry32(seed) {
  return () => {
    seed = (seed + 0x6d2b79f5) | 0;
    let r = Math.imul(seed ^ (seed >>> 15), 1 | seed);
    r = (r + Math.imul(r ^ (r >>> 7), 61 | r)) ^ r;
    return ((r ^ (r >>> 14)) >>> 0) / 4294967296;
  };
}
const n1 = (v) => v.toFixed(2);
function barcodeArt() { // 32 × 14 mm on paper: bars and no digits, so it reads as decoration
  const rand = mulberry32(14);
  let [x, bars] = [2, ''];
  while (x < 30) {
    const w = 0.25 + Math.floor(rand() * 3) * 0.25;
    if (rand() < 0.55) {
      bars += `<rect x="${n1(x)}" y="1.5" width="${w}" height="11" fill="${palette.ink}"/>`;
    }
    x += w + 0.25;
  }
  return '<svg xmlns="http://www.w3.org/2000/svg" width="320" height="140" viewBox="0 0 32 14">'
    + `<rect width="32" height="14" fill="${palette.paper}"/>${bars}</svg>`;
}
// #endregion

// ─── 3 · Fonts ──────────────────────────────────────────────────────────────
const FONTS = { // text, display and label faces, loaded before the build
  Spectral: ['400', '400i', '700'], 'Bodoni Moda': ['400', '400i', '600', '700', '900'],
  Jost: ['400', '600'] }; // Spectral 700: the contributors' names; Jost 400: the captions

// ─── 4 · Build & show ───────────────────────────────────────────────────────
const photos = [...new Set(resources.flatMap((r) => r.bitmap?.fileId ?? []))]; // each JPEG once
await Promise.all([...photos.map((file) => loadImage(file, asset(file))),
  loadSvg('barcode.svg', barcodeArt())]);
const doc = await buildDocumentWithFonts({ markdown, resources }, config(), kitFonts(FONTS));
showPages(doc, { title: t({ en: 'Magazine cover and sectioned contents',
  es: 'Portada de revista e índice por secciones' }) });

// ─── 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 · images v3 ── recipes with pictures · postext.dev/cookbook
/** Registers a photo or PNG for the canvas and keeps its bytes for the PDF.
 *  fetch → ImageBitmap never taints the canvas (a plain cross-origin <img> would). */
async function loadImage(fileId, url) {
  const res = await fetch(url);
  if (!res.ok) throw new Error(`Image not found (${res.status}): ${url}`);
  const bytes = new Uint8Array(await res.arrayBuffer());
  registerResourceImage(fileId, await createImageBitmap(new Blob([bytes])));
  (loadImage.bytes ??= new Map()).set(fileId, bytes);
}

/** Registers SVG markup as a vector image, its fonts inside (inlineSvgFonts, ≥ 1.25).
 *  `options` go to registerResourceImage: { singleInk: true } tints it on the canvas. */
async function loadSvg(fileId, svg, options) {
  if (typeof inlineSvgFonts === 'function') svg = await inlineSvgFonts(svg, svgFaces);
  const img = new Image();
  img.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
  await img.decode();
  registerResourceImage(fileId, img, options);
  (loadImage.bytes ??= new Map()).set(fileId, new TextEncoder().encode(svg));
}

/** A face's Fontsource files: nearest weight; latin + what the SVG sets. */
async function svgFaces(family, weight, style, { codePoints = [] } = {}) {
  const [id, m] = [fontsourceId(family), await fontsourceMeta(family)];
  const w = m?.weights.reduce((a, b) => (Math.abs(b - weight) < Math.abs(a - weight) ? b : a)) ?? weight;
  const s = m?.styles.includes(style) === false ? 'normal' : style;
  return Promise.all(['latin', ...kitSubsetsFor(String.fromCodePoint(...codePoints), m)].map(async (x) => new Uint8Array(
    await (await fetch(`${await fontsourceAt(id)}files/${id}-${x}-${w}-${s}.woff2`)).arrayBuffer())));
}

/** renderToPdf({ resourceBytes: imageBytes }) */
function imageBytes(fileId) { return loadImage.bytes?.get(fileId); }

/** renderToHtml({ resourceImageUrl: imageUrl }) */
function imageUrl(fileId) {
  const bytes = imageBytes(fileId);
  if (!bytes) return undefined;
  imageUrl.urls ??= new Map();
  if (!imageUrl.urls.has(fileId)) {
    const type = /\.svg$/i.test(fileId) ? 'image/svg+xml' : /\.png$/i.test(fileId) ? 'image/png' : 'image/jpeg';
    imageUrl.urls.set(fileId, URL.createObjectURL(new Blob([bytes], { type })));
  }
  return imageUrl.urls.get(fileId);
}

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

## Varianten

### Das Logo in der Hausfarbe setzen

Das Logo nimmt das Erdbraun der großen 14 auf der Inhaltsseite an.

```diff
-    textTransform: 'uppercase', align: 'center', color: col('ink') },
+    textTransform: 'uppercase', align: 'center', color: col('band') },
```

### Jede Rubrik auf einer Trennseite eröffnen

Für Teile mit eigener Seite und einer Seitenzahl in jeder Teilzeile des Inhaltsverzeichnisses siehe [Teile in Farbe aus einem einzigen Attribut](https://postext.dev/de/cookbook/parts-in-colour.md).

## Häufige Fehler

- **Ein 'top'-Gleitobjekt kommt auf die Seite nach seinem Verweis, außer es darf die verweisende Seite eröffnen.** Standardmäßig steht ein Gleitobjekt nie über seinem eigenen Verweis, sodass ein seitenbreites 'top'-Gleitobjekt, auf das auf Seite N verwiesen wird, Seite N+1 eröffnet. Setzen Sie placement.citingPage: true an der Ressource oder am defaultPlacement ihres Typs (oder layout.floatsAtCitingPage für das ganze Buch, seit postext 1.25), dann steht es stattdessen oben auf Seite N, und der Text über dem Verweis rückt darunter, sofern es in layout.maxTopFraction passt und die verweisende Zeile auf dieser Seite bleibt. Es eröffnet nie die erste Seite eines Kapitels oder eine Seite nach einem erzwungenen Umbruch: dort eröffnet es weiterhin die nächste Seite, und 'auto' oder 'bottom' können den Fuß der verweisenden Seite einnehmen.
- **Bitmaps beziehen ihre Druckgröße aus einer Auflösung: diese angeben, nicht Pixel in Druckgröße.** Eine Bitmap ohne Auflösung wird mit einem Pixel pro Layoutpixel bei der dpi des Dokuments gesetzt, sodass ein Foto mit 2400 px auf einer Seite mit 150 dpi 406 mm breit ist. Geben Sie die echten Pixel der Datei an und dem Bild seine Auflösung (bitmap.resolution: 300), oder setzen Sie layout.bitmapResolution für jede Bitmap: einen ppi-Wert oder 'file', um den in der Datei angegebenen zu lesen. Die Spalte begrenzt das Bild weiterhin.

- Ein Bildelement passt sein Bild in seinen Kasten ein und beschneidet es nie. Schneiden Sie jedes JPEG zuerst auf seinen Kasten zu, 23 : 30 für den Titel und 230 × 150 mm für die Auftakte, sonst schrumpft das Bild und lässt weiße Streifen an den Rändern.
- Im Text färbt ein Teil jede Farbe um, deren Hexwert dem Grund-Hexwert von `band` gleicht, ob sie auf `band` verweist oder nicht. Behalten Sie diesen Hexwert dem vor, was sich mit der Rubrik ändern soll.
- Die Beiträge sind in beiden Sprachen eingepasst: Einige Sätze der spanischen „Field notes“ wurden gekürzt, damit sie auf einer Seite bleiben, und ein Satz der englischen Titelgeschichte verlor drei Wörter, damit beide Spalten von Seite 4 auf gleicher Höhe enden. Prüfen Sie nach einer Änderung, dass kein Beitrag ein paar Zeilen auf eine neue Seite verschiebt und dass jede Seite außer der letzten eines Beitrags auf gleicher Höhe endet.

## Nachweise

- Rezept: Ignacio Ferro ([@drnachio](https://github.com/drnachio))
- Bilder: Castellina in Chianti (the cover and its copy in the contents): Rowan Heuvel ([Quelle](https://commons.wikimedia.org/wiki/File:Castellina_In_Chianti_(Unsplash).jpg)), CC0-1.0
- Bilder: Castellina in Chianti, a detail (the feature's photograph): Rowan Heuvel ([Quelle](https://commons.wikimedia.org/wiki/File:Castellina_In_Chianti_(Unsplash).jpg)), CC0-1.0
- Bilder: Plants in beakers (the Field notes opener): chuttersnap ([Quelle](https://commons.wikimedia.org/wiki/File:Plants_in_beakers_(Unsplash).jpg)), CC0-1.0
- Bilder: Harvesting the wheat crop (the Kitchen opener): meriç tuna ([Quelle](https://commons.wikimedia.org/wiki/File:Harvesting_the_Wheat_Crop_(Unsplash).jpg)), CC0-1.0
- Bilder: Fields from above near Castellina in Chianti (the feature's opener): Generated With Diffusion Models, Original
- Bilder: The cover's barcode, drawn in code: Ignacio Ferro, MIT
- Schrift: Spectral (OFL-1.1), Bodoni Moda (OFL-1.1), Jost (OFL-1.1)
- Code: MIT · Beispielinhalt: CC-BY-4.0

## Verwandte Rezepte

- [Nº 004 · Magazinreportage: vom Fotoauftakt bis zum Schlusszeichen](https://postext.dev/de/cookbook/magazine-feature-opener.md): Ein Auftakt mit advancedDesign setzt ein randabfallendes Foto und Dachzeile, Titel, Vorspann und Autorenzeile der Überschrift; dann zwei Kästen, ein Chip. · Stufe 3 (Fortgeschritten) · Magazine und Zines
- [Nº 051 · Riso-Zine in zwei Farben: Grafik in einer Sonderfarbe](https://postext.dev/de/cookbook/riso-zine-single-ink.md): Ein vierseitiges Zine für einen Risographen mit zwei Trommeln: farbige Zeichnungen drucken als Pinktöne, umgefärbt von der Engine im Canvas und im PDF. · Stufe 3 (Fortgeschritten) · Magazine und Zines
- [Nº 027 · Titelseite einer Zeitung](https://postext.dev/de/cookbook/newspaper-front-page.md): Titelseite und Seite 2 eines Lokalblatts in zwei Spalten mit Linie, seitenbreite Schlagzeilen als Kästen und ein vierspaltiger Streifen Kurzmeldungen am Fuß. · Stufe 3 (Fortgeschritten) · Zeitungen und Newsletter
