# 雑誌の表紙とセクション別の目次

> 写真の表紙では表紙コピーが題字から吊り下がり、目次はセクションごとに色の行を立てて生成されます。各セクションは、ページを開かない部です。

- HTML版: https://postext.dev/ja/cookbook/magazine-cover-and-contents
- レシピ No. 023 · 完成した出版物 · 難易度 3 (上級) · 出力: Canvas
- ジャンル: 雑誌とジン
- 必要なもの postext ≥ 1.4.1 · テスト環境 1.9.2 ／テスト日 2026-10-01
- ページ: [1](https://postext.dev/cookbook/magazine-cover-and-contents/en/p01.webp?v=bcc05980), [2](https://postext.dev/cookbook/magazine-cover-and-contents/en/p02.webp?v=bcc05980), [3](https://postext.dev/cookbook/magazine-cover-and-contents/en/p03.webp?v=bcc05980), [4](https://postext.dev/cookbook/magazine-cover-and-contents/en/p04.webp?v=bcc05980), [5](https://postext.dev/cookbook/magazine-cover-and-contents/en/p05.webp?v=bcc05980), [6](https://postext.dev/cookbook/magazine-cover-and-contents/en/p06.webp?v=bcc05980)
- Sandboxで開く: https://postext.dev/ja/sandbox#recipe=magazine-cover-and-contents&lang=en (.postext: https://postext.dev/cookbook/magazine-cover-and-contents/en/magazine-cover-and-contents.postext)
- 最終更新: 2026-09-26
- 他の言語: [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), [zh](https://postext.dev/zh/cookbook/magazine-cover-and-contents.md), [ar](https://postext.dev/ar/cookbook/magazine-cover-and-contents.md)

## かんたんな説明

食と農業の雑誌の表紙と最初の数ページです。写真の表紙と目次があり、セクションごとに色を決めて、その色をそのページ全体に通す方法がわかります。

## できあがり

架空の食と土地の季刊誌『FALLOW』のある号から、230 × 300mmの6ページです。表紙では118ptのBodoniの題字の下にトスカーナの谷が裁ち落としで広がり、3本の表紙コピーが紙色のパネルに載ります。キッカーはそれぞれセクションの色で、隅に価格とバーコードがあります。2ページ目は生成した目次で、セクションごとに色のタブを立て、その下に記事の題、ノンブル、リードを並べます。右の段には編集長の言葉、寄稿者、奥付が入ります。続くセクションはテラコッタ、オリーブ、ワイン色です。どの記事も、ページの頭に裁ち落としで渡した写真と、その下端に食い込むセクションの色のパネルで始まり、ノンブルのタブ、行頭記号、囲みもその色になります。色を決めるのは各記事の上の`:::part`フェンスで、区切りのページは開きません。

**このレシピが答える問い:**

- 雑誌の表紙と奥付はどう作り、半扉や扉の代わりになるものは何ですか？
- 自動で更新される目次（リーダー、ノンブル、著者名、部の行）を追加するには？
- 本を部や節に分け、それぞれに専用の色と中扉を付けるには？
- 章ごとに異なる写真、色、章扉のバリエーションを与えるには？

## 手短な答え

```js
// script.js, 行 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, // a multiple of the size (gotcha: design-lineheight-multiple)
    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="…"}
```

## 材料

**学べること**

- [デザイン要素の基準位置](https://postext.dev/ja/docs/configuration.md#要素の配置): 座標ではなく、コンテナー、ページ、裁ち落とし、または別の要素（right-of、below、align-*）を基準に要素を配置します。
- [目次](https://postext.dev/ja/docs/configuration.md#目次): 見出しから作る:::tocです。点線のリーダー、印刷どおりのページラベル、著者名の行、色付きの部の行、クリックできるPDFの項目を持ちます。
- [部ごとの色](https://postext.dev/ja/docs/configuration.md#partコンテナー): 部や見出しスタイルがパレットの項目を上書きするので、帯、柱、本文のアクセントがその節の色になります。

**ほかに使うもの**

- [節ごとの柱](https://postext.dev/ja/docs/configuration.md#見出しスタイル)
- [デザインした章扉](https://postext.dev/ja/docs/configuration.md#幅と詳細デザイン)
- [見出しスタイル](https://postext.dev/ja/docs/configuration.md#見出しスタイル)
- [番号のない章](https://postext.dev/ja/docs/configuration.md#見出しスタイル)
- [ページデザインの画像](https://postext.dev/ja/docs/configuration.md#画像要素)
- [ページデザインのテキスト・罫・ボックス](https://postext.dev/ja/docs/configuration.md#柱とノンブル)
- [見出しの属性](https://postext.dev/ja/docs/document-format.md#見出しの属性)
- [セマンティックカラーパレット](https://postext.dev/ja/docs/configuration.md#カラーパレット)
- [段抜きの章見出し帯](https://postext.dev/ja/docs/configuration.md#幅と詳細デザイン)
- [図の配置](https://postext.dev/ja/docs/document-format.md#配置)
- [フロートする囲み](https://postext.dev/ja/docs/configuration.md#calloutコンテナー)
- [囲み](https://postext.dev/ja/docs/configuration.md#囲みスタイル)
- [段落スタイル](https://postext.dev/ja/docs/configuration.md#段落スタイル)
- [独自のリソースの種類](https://postext.dev/ja/docs/configuration.md#リソースの種類)
- [リソースとしての図と表](https://postext.dev/ja/docs/document-format.md#リソース)
- [キャンバス上のページ](https://postext.dev/ja/docs/configuration.md#ページをビットマップに描画する)
- [その場に置く図](https://postext.dev/ja/docs/document-format.md#ブロック埋め込み任意本文中への明示的な配置)
- [用紙の色](https://postext.dev/ja/docs/configuration.md#ページ)
- [改ページと改段](https://postext.dev/ja/docs/document-format.md#pagebreak)
- [部扉](https://postext.dev/ja/docs/configuration.md#partコンテナー)
- [明示的な縦のアキ](https://postext.dev/ja/docs/document-format.md#space)

**設定の一覧**

- [`bodyText`](https://postext.dev/ja/docs/configuration.md#本文), [`calloutStyles`](https://postext.dev/ja/docs/configuration.md#囲みスタイル), [`captionStyle`](https://postext.dev/ja/docs/configuration.md#キャプションスタイル), [`colorPalette`](https://postext.dev/ja/docs/configuration.md#カラーパレット), [`footer`](https://postext.dev/ja/docs/configuration.md#柱とノンブル), [`header`](https://postext.dev/ja/docs/configuration.md#柱とノンブル), [`headingStyles`](https://postext.dev/ja/docs/configuration.md#見出しスタイル), [`headings`](https://postext.dev/ja/docs/configuration.md#見出し), [`layout`](https://postext.dev/ja/docs/configuration.md#レイアウト), [`locale`](https://postext.dev/ja/docs/configuration.md#ハイフネーション), [`orderedLists`](https://postext.dev/ja/docs/configuration.md#番号付きリスト), [`page`](https://postext.dev/ja/docs/configuration.md#ページ), [`paragraphStyles`](https://postext.dev/ja/docs/configuration.md#段落スタイル), [`parts`](https://postext.dev/ja/docs/configuration.md#部), [`resourceTypes`](https://postext.dev/ja/docs/configuration.md#リソースの種類), [`toc`](https://postext.dev/ja/docs/configuration.md#目次), [`unorderedLists`](https://postext.dev/ja/docs/configuration.md#箇条書きリスト)

**API**

- [`buildDocument`](https://postext.dev/ja/docs/configuration.md#文書のビルド), [`clearMeasurementCache`](https://postext.dev/ja/docs/configuration.md#計測キャッシュ), [`registerResourceImage`](https://postext.dev/ja/docs/architecture.md#apiの概要), [`renderPageToCanvas`](https://postext.dev/ja/docs/configuration.md#ページをビットマップに描画する)

**書体**

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

## 作り方

### 1 · 表紙を題字から吊り下げる

コードは上の[手短な答え](#手短な答え)にあります。ページに固定しているのは写真、頭の2行、価格だけです。それ以外はすべてほかの要素に固定しています（[要素の配置](/ja/docs/configuration#要素の配置)）。題字はストラップの下、最初のキッカーは題字の8mm下、そして各コピーはそのキッカーの下、各キッカーは前のコピーの下です。そのため表紙コピーが長くなったり題字が大きくなったりしても、下にあるものは重なって印字されず、押し下げられます。キッカーとコピーはそれぞれ幅84mmの紙色のボックスを持ち、6つのボックスが積み重なってパネルになるので、パネルは言葉に合わせて伸びます。最初のボックス以外は上のボックスに0.3mm重ね（`SEAM`）、バーコードも同じように価格に重ねます。Canvasでは、接しているだけの2つの塗りのあいだに、写真がヘアラインのように見えてしまうからです。雑誌には半扉も扉もありません。誌名は表紙が、号数、季節、奥付は目次ページが受け持ちます。書籍の前付けについては[前付けはローマ数字、本文は1ページから](https://postext.dev/ja/cookbook/front-matter-roman-to-arabic.md)を参照してください。

### 2 · 各セクションを、ページを持たない部にする

```js
// script.js, 行 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'),
];
```

`parts.page: false`にすると、`:::part{title="Features" palette="band=#5d6a2b"}`のようなフェンスは区切りのページを開かず、次のブロックから先のセクションの題とパレットを設定するだけです（[`:::part`コンテナー](/ja/docs/configuration#partコンテナー)）。各記事のH1は`numbered: false`の[見出しスタイル](/ja/docs/configuration#見出しスタイル)を取るので、目次は番号なしで並べます。スタイルが冒頭の写真を選び、部が色を受け持ちます。

### 3 · セクションの色はすべて1つの項目にリンクする

```js
// script.js, 行 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; designs paint the hex in 1.4.1 (gotcha: palette-skips-designs).
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' } },
];
```

部はパレットの項目をidで上書きするので、セクションとともに変わるものはすべて`band`にリンクします。冒頭のパネル、ノンブルのタブ、行頭記号とリストの番号、プルクオート、データ囲みの帯と題です。本文では、上書きは値で行われます。hexが`band`の基本のhex（`#8b6a3e`）と同じ色はすべてセクションの色になるので、ほかの項目はこのhexを使いません。デザイン要素は逆です。Postext 1.4.1は`paletteId`の横に書いたhexで塗り、`colorPalette`を無視しますが、部の上書きは`paletteId`を通じてデザイン要素にも届くので、`col()`は両方を書きます。

### 4 · 目次を自分でグループ分けさせる

```js
// script.js, 行 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}: a part with no divider page has no page (gotcha: toc-part-rows-no-label).
  parts: { height: pt(2 * LEAD), design: { elements: [ // two grid lines (1.4.1's 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`は記事を並べ、各`:::part`に、`toc.parts.design`からその部のパレットで組んだ行を与えます（[目次](/ja/docs/configuration#目次)）。そのため1つの行デザインで、3色のタブが3つ印字されます。罫とタブはどちらも`mid`、つまり行の中心を1mm上げた位置から吊り下がり、タブが下の題にかからないようにしています。リーダーをオフにしているので、題とノンブル（3、4、6）のあいだには何も入らず、`subtitle.attr`が冒頭の見出しの`standfirst`属性を題の下にイタリックで印字します。右の段の最後にある奥付は段落スタイルで、`muted`の色のJost 7ptです。Markdownでは`:::paragraphs{style="colophon"}`で適用します。

### 5 · どの記事にも同じ扉を

```js
// script.js, 行 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 picture reserves nothing (gotcha: opener-image-no-reserve); the panel and the byline
  // reach below it, and the byline's empty padding keeps the story 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')),
] } });
```

写真はページの天から裁ち落としで、150mm下まで届きます。`band`のパネルが、その下端に34mm食い込みます。パネルは幅136mmの3つのテキストボックスで、それぞれ前のボックスの`below`に連なり、`{partTitle}`から取ったセクション名、題、リードを収めます。写真は高さを確保しないので、記事は高さを確保する要素のうち最も低いもの、つまり署名の下から始まります。署名の空の5mmの下パディングによって、最初の行は少なくともそれだけ署名から離れます。

### 6 · ノンブルにセクション名を添える

```js
// script.js, 行 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
  }) });
```

ノンブルは小口側の地にある6.5mm角の`band`の四角に入ります。セクション名はその横、のど寄りに固定し、記事の題はさらにその横に置きます。3つのボックスはどれも高さ6.5mmで、文字を上下中央にそろえているので、同じ中心線を共有します。雑誌は記事の最初のページにもノンブルを印字するので、フッターに`pages`の絞り込みはありません。表紙の見出しスタイルはフッターを空にし、目次のスタイルはセクションと記事の位置に誌名と号の季節（`{title}`と`{publishDate}`）を置きます。

## レシピの全体

レシピのフォルダーから合成した1つのファイルで、サンプルのテキストとレシピ集の共通キットを埋め込んであり、自分でページを組み立てます。動かすには、空のページの`<script type="module">`に入れるか、新しいCodePenのpenのJSパネルに貼り付けます（モジュールとして）。esm.shからpostextを読み込むので、インストールもビルドも要りません。

- レシピのフォルダー: 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.4.1
import {
  buildDocument, renderPageToCanvas, clearMeasurementCache, 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; designs paint the hex in 1.4.1 (gotcha: palette-skips-designs).
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, // a multiple of the size (gotcha: design-lineheight-multiple)
    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}: a part with no divider page has no page (gotcha: toc-part-rows-no-label).
  parts: { height: pt(2 * LEAD), design: { elements: [ // two grid lines (1.4.1's 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 picture reserves nothing (gotcha: opener-image-no-reserve); the panel and the byline
  // reach below it, and the byline's empty padding keeps the story 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 = () => ({ // a factory: the engine caches resolved configs per object
  locale: t({ en: 'en-us', es: 'es' }), // exact codes (gotcha: hyphenation-locales)
  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
    maxRuntTracking: 0 }, // tracking 1.4.1 never paints (gotcha: runt-tracking-unpainted)
  headings: { fontFamily: 'Bodoni Moda', marginBottom: pt(0), levels: [
    // Restated (gotcha: headings-drop-h1-break); 'any': a story opens on the very next page.
    { 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
  calloutStyles: [{ id: 'quote', ...boxStyle, marginTop: pt(LEAD), // the floated box has none
    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 (gotcha: fonts-first)
  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 ───────────────────────────────────────────────────────
await loadFonts(FONTS, markdown);
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 buildWithFonts(() => buildDocument({ markdown, resources }, config()), markdown);
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 v1 ── the same in every recipe · postext.dev/cookbook ────────
// Postext measures text with the faces the browser has loaded, and caches the
// widths, so every face must be ready before the first build. Faces come from
// Fontsource: the same static files the PDF embeds, so screen and PDF agree.

/** faces = { 'Family Name': ['400', '400i', '700'] }. `text` is the sample:
 *  letters beyond Latin-1 (č, ł, ő…) also load the latin-ext files. With
 *  `optional`, a face Fontsource does not ship is skipped instead of failing.
 *  Resolves to the number of faces added. */
async function loadFonts(faces, text = '', { optional = false } = {}) {
  kitStatus('Loading fonts…');
  const ranges = {
    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',
  };
  const subsets = /[Ā-˿Ḁ-ỿ]/.test(text) ? ['latin', 'latin-ext'] : ['latin'];
  const jobs = [];
  let added = 0;
  for (const [family, specs] of Object.entries(faces)) {
    const id = fontsourceId(family);
    const meta = optional ? await fontsourceMeta(family) : null;
    for (const spec of new Set(specs)) {
      const weight = parseInt(spec, 10);
      const style = spec.endsWith('i') ? 'italic' : 'normal';
      if (hasFace(family, weight, style)) continue;
      if (optional && !(meta?.weights.includes(weight) && meta.styles.includes(style))) continue;
      for (const subset of subsets) {
        const url = `https://cdn.jsdelivr.net/npm/@fontsource/${id}@5/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().then((ready) => { document.fonts.add(ready); added++; }, () => {
          if (subset === 'latin' && !optional) throw new Error(`Fontsource has no ${family} ${weight} ${style}`);
        }));
      }
    }
  }
  await Promise.all(jobs).catch((error) => { kitFail(error); throw error; });
  return added;
}

/** Runs `build` (a buildDocument or buildBundle call) and checks the faces
 *  the pages use. A regular face missing from FONTS is loaded with a warning;
 *  bold and italic variants are loaded when the family ships them. Then the
 *  measurement caches are cleared and the build runs again. */
async function buildWithFonts(build, text = '') {
  const tried = new Set();
  for (let round = 0; round < 3; round++) {
    kitStatus('Laying out…');
    await new Promise(requestAnimationFrame);          // let the status paint first
    const result = await Promise.resolve().then(build).catch((error) => { kitFail(error); throw error; });
    const wanted = { base: {}, variants: {} };
    for (const { font, base } of [result].flat().flatMap(fontStringsOf)) {
      const { family, weight, style } = parseFont(font);
      const key = `${family}|${weight}|${style}`;
      if (tried.has(key) || hasFace(family, weight, style)) continue;
      tried.add(key);
      (wanted[base ? 'base' : 'variants'][family] ??= []).push(`${weight}${style === 'italic' ? 'i' : ''}`);
    }
    if (Object.keys(wanted.base).length) {
      console.warn(`[cookbook] FONTS does not list ${JSON.stringify(wanted.base)}: loading them.`);
    }
    const added = await loadFonts(wanted.base, text) + await loadFonts(wanted.variants, text, { optional: true });
    if (added === 0) return result;
    clearMeasurementCache();
  }
  throw new Error('The fonts did not settle after three builds.');
}

/** Every font string of the layout. `base` marks a block's own face; its
 *  bold, italic and bold-italic variants are listed whether or not used. */
function fontStringsOf(doc) {
  const found = new Map();
  const walk = (node) => {
    if (!node || typeof node !== 'object') return;
    if (Array.isArray(node)) { node.forEach(walk); return; }
    for (const [key, value] of Object.entries(node)) {
      if (typeof value === 'string' && /fontString$/i.test(key)) {
        found.set(value, found.get(value) || key === 'fontString');
      } else if (value && typeof value === 'object') walk(value);
    }
  };
  walk(doc.pages);
  walk(doc.blocks);
  return [...found].map(([font, base]) => ({ font, base }));
}

/** '700 37.5px Open Sans' / 'italic 400 13px "Source Serif 4"' → { family, weight, style }.
 *  A string with no weight ('95.8px Young Serif', from a design text) is 400. */
function parseFont(font) {
  const m = /^(?:(italic|oblique)\s+)?(?:small-caps\s+)?(?:(\d+|bold|normal)\s+)?[\d.]+px\s+(.+)$/.exec(font.trim());
  if (!m) throw new Error(`Unexpected font string: ${font}`);
  const weight = m[2] === 'bold' ? 700 : !m[2] || m[2] === 'normal' ? 400 : Number(m[2]);
  return { family: m[3].replace(/^["']|["']$/g, ''), weight, style: m[1] ? 'italic' : 'normal' };
}

/** True when a loaded FontFace covers exactly this family, weight and style
 *  (document.fonts.check() is also true 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;
}

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

/** The weights and styles a family ships ({ weights: [400, 700], styles: ['normal', 'italic'] }), 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 v1 ── the same in every recipe · postext.dev/cookbook ───────
/** Shows the pages as facing spreads on a dark desk: the first page is a
 *  recto on its own, then verso | recto pairs, as in a bound book. Pages
 *  are painted when they scroll near the screen. */
function showPages(docs, { title, width = 460 } = {}) {
  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, { 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 v1 ── 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 (drawn in code, or fetched) as a vector image. */
async function loadSvg(fileId, svg) {
  const img = new Image();
  img.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
  await img.decode();
  registerResourceImage(fileId, img);
  (loadImage.bytes ??= new Map()).set(fileId, new TextEncoder().encode(svg));
}

/** 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 ───────────────────────────────────────────────────────────────────────
```

## アレンジ

### 題字をハウスカラーで組む

題字を、目次ページの大きな14と同じ土色にします。

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

### 各セクションを区切りのページで始める

独自のページを持つ部と、目次の各部の行にノンブルを付ける方法は、[ひとつの属性で部ごとに色を変える](https://postext.dev/ja/cookbook/parts-in-colour.md)を参照してください。

## よくあるつまずき

- **部扉のない部の行にはページ番号がない.** parts.page: falseでは部扉のページがないため、目次の部の行に出すページ番号がありません。その場合は部の行のデザインから{pageNumber}を外してください。
- **パレットを差し替えても、デザイン要素と参照色は変わらない.** postext 1.4.1はcolorPaletteをテキストのスタイル（本文、見出し、リスト、キャプション、表、囲み）には反映しますが、ヘッダー、フッター、章扉、部扉の要素と、bodyText.referenceColorには反映しません。これらはpaletteIdの横に書いた16進の色のままです。画面用のダーク版や色替えのためにパレットを差し替えるときは、ビルドの前に、リンクしたすべての色をcolorPaletteから書き直してください。
- **章扉の画像は確保する高さに数えられない.** postext 1.4.1では、詳細デザインの見出しは、確保する高さを画像抜きで計測します。テキスト、罫、箱はページに固定していても数えられますが、ページの頭に裁ち落としで置いた写真のような画像は何も確保しないため、本文がその上から始まることがあります。minHeightを本文が始まるべき位置に設定してください。
- **headingsオブジェクトを渡すとH1の改ページが消える.** 既定ではH1は奇数ページへ改ページします（always-odd）。ところがheadingsオブジェクトを渡すと中身にかかわらずこの既定がリセットされ、章は改ページせずに続けて組まれ、span: 'page'も効かなくなります。どの設定でもheadings.levels[0].breakBefore: { enabled: true, parity }を書き直してください。
- **'top'のフロートは参照したページには入らない.** フロートが自分の参照より上に置かれることはありません。そのため、Nページで参照したページ幅の'top'フロートはN+1ページの頭に置かれます。もっと前で参照するか、参照したページの下端に入れる位置'auto'または'bottom'を使ってください。
- **ビットマップは文書のdpiでのpxで配置される。印刷サイズを宣言する.** ビットマップのリソースの大きさは、ファイルからではなく、宣言した幅と高さ（文書のdpiでのピクセル数）から決まります。印刷サイズのピクセル数（刷り上がりの幅で約300 dpi）を宣言すれば、図は正しい大きさに収まり、鮮明さも保たれます。
- **デザインのテキストのlineHeightは倍率で、寸法ではない.** デザインのスロットでは、テキスト要素のlineHeightはフォントサイズに掛ける倍率です（lineHeight: 1.05）。postext 1.4.1ではpt(15)のような寸法を指定しても拒否されず、章扉の高さがNaNと計測されて、minHeightを含め確保する高さが警告なしに失われ、本文がタイトルに重なって組まれます。
- **ラントの修正が、描かれないトラッキングを詰めることがある.** postext 1.4.1では、段落がラント（最終行に残った短い一語）で終わると、レイアウトはその段落を1行短く組みます。まず語間を詰め、次にmaxRuntTracking（1000分の1 em単位）までの負のトラッキングを使います。CanvasとPDFのレンダラーは0より大きいトラッキングしか描かないため、トラッキングを詰めた段落はトラッキングなしで印字されます。両端そろえの行はその差の分だけ語間を失って詰まって見え、最終行は行長を超えて段の端で切れることがあります。bodyText.maxRuntTracking: 0を設定すれば語間による修正は残るので、それでも出るラントは文を書き換えてください。
- **ハイフネーションできるのは8つのロケールだけで、コードは完全一致.** ハイフネーションが用意されているのはen-us、es、fr、de、it、pt、ca、nlで、コードは完全一致で照合されます。'es-ES'やほかの言語は、何の知らせもなくアメリカ英語にフォールバックします。
- **レイアウトの前にすべてのフォントを読み込む.** レイアウトはブラウザーが読み込んだフォントで文字を計測し、その幅をキャッシュします。最初のビルドのあとに届いたフォントがあると改行位置が狂い、PDFも画面と一致しなくなります。すべてのウェイトとスタイルを先に読み込み、遅れて届いたときは再ビルドの前にclearMeasurementCache()を呼んでください。

- 画像要素は写真をボックスの中に収め、切り抜くことはありません。各JPEGをあらかじめボックスに合わせて切っておきます。表紙は23 : 30、扉は230 × 150mmです。そうしないと写真が縮み、縁に白い帯が残ります。
- 本文では、部は`band`にリンクしているかどうかにかかわらず、hexが`band`の基本のhexと同じ色をすべて塗り替えます。このhexは、セクションとともに変えたいものだけに使ってください。
- 記事は両方の言語でコピーフィットしています。スペイン語版の「Field notes」は1ページに収まるようにいくつかの文を短くし、英語版の特集は4ページ目の両段の末尾がそろうように1文から3語を削りました。編集したあとは、数行だけ新しいページにはみ出す記事がないこと、そして記事の最終ページ以外のすべてのページで段末がそろっていることを確認してください。

## クレジット

- レシピ: Ignacio Ferro ([@drnachio](https://github.com/drnachio))
- 画像: Castellina in Chianti (the cover and its copy in the contents): Rowan Heuvel ([出典](https://commons.wikimedia.org/wiki/File:Castellina_In_Chianti_(Unsplash).jpg)), CC0-1.0
- 画像: Castellina in Chianti, a detail (the feature's photograph): Rowan Heuvel ([出典](https://commons.wikimedia.org/wiki/File:Castellina_In_Chianti_(Unsplash).jpg)), CC0-1.0
- 画像: Plants in beakers (the Field notes opener): chuttersnap ([出典](https://commons.wikimedia.org/wiki/File:Plants_in_beakers_(Unsplash).jpg)), CC0-1.0
- 画像: Harvesting the wheat crop (the Kitchen opener): meriç tuna ([出典](https://commons.wikimedia.org/wiki/File:Harvesting_the_Wheat_Crop_(Unsplash).jpg)), CC0-1.0
- 画像: Fields from above near Castellina in Chianti (the feature's opener): Generated With Diffusion Models, オリジナル
- 画像: The cover's barcode, drawn in code: Ignacio Ferro, MIT
- 書体: Spectral (OFL-1.1), Bodoni Moda (OFL-1.1), Jost (OFL-1.1)
- コード: MIT · サンプルの内容: CC-BY-4.0

## 関連レシピ

- [No. 004 · 雑誌の特集：写真の扉からエンドマークまで](https://postext.dev/ja/cookbook/magazine-feature-opener.md): 1つのadvancedDesignの扉が、裁ち落としの写真と、見出しのキッカー、見出し、リード、署名を組みます。続いて2つのフロートの囲みとチップのエンドマーク。 · 難易度 3 (上級) · 雑誌とジン
- [No. 051 · 2色リソのZine：図版を1色の特色で](https://postext.dev/ja/cookbook/riso-zine-single-ink.md): 2ドラムのリソグラフ用の4ページのZineです。フルカラーの線画はピンクの濃淡で印刷され、CanvasではCodePenのサンプルが、PDFではrenderToPdfが色を置き換えます。 · 難易度 3 (上級) · 雑誌とジン
- [No. 027 · 新聞の第1面](https://postext.dev/ja/cookbook/newspaper-front-page.md): 地方週刊紙の第1面と2ページ目を罫で区切った2段で組み、ページ幅の大見出しは囲みとして組み、4段の短信欄は下端にフロートさせます。 · 難易度 3 (上級) · 新聞とニュースレター
