# مجلة صغيرة مدبّسة بورقة غلاف من الكرتون

> سياجا :::paper يضعان كرتونًا بلون السلمون على الورقة الخارجية لمجلة صغيرة من ثماني صفحات، ويعرضها postext-folio مدبّسة على الطيّ حول صفحات من ورق الصحف.

- نسخة HTML: https://postext.dev/ar/cookbook/saddle-stitched-card-cover
- وصفة رقم 102 · الإخراج والدمج · المستوى 2 (متوسط) · المخرجات: Canvas, عناصر تحكم حية
- الأنواع: المجلات والمجلات المستقلة
- تتطلب postext ≥ 1.13.4 · اختُبرت مع 1.14.0 بتاريخ 2026-10-04
- الصفحات: [1](https://postext.dev/cookbook/saddle-stitched-card-cover/en/p01.webp?v=1319adb0), [2](https://postext.dev/cookbook/saddle-stitched-card-cover/en/p02.webp?v=1319adb0), [3](https://postext.dev/cookbook/saddle-stitched-card-cover/en/p03.webp?v=1319adb0), [4](https://postext.dev/cookbook/saddle-stitched-card-cover/en/p04.webp?v=1319adb0), [5](https://postext.dev/cookbook/saddle-stitched-card-cover/en/p05.webp?v=1319adb0), [6](https://postext.dev/cookbook/saddle-stitched-card-cover/en/p06.webp?v=1319adb0), [7](https://postext.dev/cookbook/saddle-stitched-card-cover/en/p07.webp?v=1319adb0), [8](https://postext.dev/cookbook/saddle-stitched-card-cover/en/p08.webp?v=1319adb0)
- افتح في Sandbox: https://postext.dev/ar/sandbox#recipe=saddle-stitched-card-cover&lang=en (.postext: https://postext.dev/cookbook/saddle-stitched-card-cover/en/saddle-stitched-card-cover.postext)
- آخر تحديث: 2026-10-04
- لغات أخرى: [en](https://postext.dev/en/cookbook/saddle-stitched-card-cover.md), [es](https://postext.dev/es/cookbook/saddle-stitched-card-cover.md), [ca](https://postext.dev/ca/cookbook/saddle-stitched-card-cover.md), [zh](https://postext.dev/zh/cookbook/saddle-stitched-card-cover.md)

## باختصار

مجلة صغيرة مطوية عن نهر، بغلاف ملون سميك وورق صحف رقيق في الداخل. في العارض ثلاثي الأبعاد تستقر مغلقة على المكتب وتُفتح كما تُفتح المجلة الحقيقية.

## ما الذي ستنضده

العدد 3 من *Low Tide*، مجلة صغيرة مجانية عن مصبّ نهر: ثماني صفحات بقياس 140 × 200 مم، ورقتان مطويتان ومدبّستان عبر الطيّ. الورقة الخارجية، الصفحات 1–2 و7–8، كرتون بلون السلمون؛ والورقة الداخلية، الصفحات 3–6، ورق صحف رخيص. يُطبع الغلاف بلون riso أزرق واحد على الكرتون العاري، من غير مساحة لونية خاصة به، فيُطبع على أبيض في الصفحات المسطحة أدناه، ويستقر في Folio مغلقًا على المكتب بلون كرتونه إلى أن تقلبه. الورقان سياجان في Markdown، والتجليد ثلاثة إعدادات في `config.folio`.

**تجيب هذه الوصفة عن:**

- كيف أطبع غلافًا أو ملحقًا أو ملزمة لوحات على ورق مختلف عن بقية الكتاب؟
- كيف أعاين تجليد الكتاب وغلافه وورقه وإضاءته قبل إرساله إلى الطباعة؟

## الجواب المختصر

```js
// script.js, سطرًا 235–246
const stage = document.createElement('section');
stage.id = 'folio';
stage.ariaLabel = 'The zine in 3D';
stage.style.cssText = 'height: min(78vh, 720px); margin: 0 auto; max-width: 1280px';
document.getElementById('pages').before(stage);
// Covers from the pages need an even count: page 8 must fall on a verso to be the back.
if (doc.pages.length % 2) console.warn(`${doc.pages.length} pages: the back cover is missing`);
createFolioFromDocument(stage, doc, {
  appearance: { textureBaseUrl: 'https://postext.dev/folio/textures' },
  onChange: ({ pages }) => kitStatus(pages.map((i) => `p. ${i + 1} on `
    + (doc.pages[i].paper ? 'card' : 'newsprint')).join(' · ')),
});
```

## المكونات

**تعلّم**

- [ورق مختلف لمجموعة من الصفحات](https://postext.dev/ar/docs/document-format.md#paper): تطبع الحاوية :::paper الصفحات التي تضمّها على ورقها الخاص: ملزمة لوحات لامعة، أو غلاف من الورق المقوّى، أو بضع أوراق ملوّنة.
- [الورق والتجليد والمكتب والإضاءة (Folio)](https://postext.dev/ar/docs/configuration.md#عارض-folio-الإعدادات): يحدّد config.folio كيف يُصنع الكتاب ثلاثي الأبعاد ويُضاء: نوع الورق ووزنه، والتجليد والأغلفة، والسطح الذي تحته، والإضاءة.

**تستخدم أيضًا**

- [عارض الكتاب ثلاثي الأبعاد (Folio)](https://postext.dev/ar/docs/configuration.md#كتاب-ثلاثي-الأبعاد-postext-folio)
- [أنماط العناوين](https://postext.dev/ar/docs/configuration.md#أنماط-العناوين)
- [صفحات افتتاح مصمَّمة](https://postext.dev/ar/docs/configuration.md#الامتداد-والتصميم-المتقدم)
- [سمات العنوان](https://postext.dev/ar/docs/document-format.md#سمات-العناوين)
- [النصوص والخطوط والمربعات في تصاميم الصفحة](https://postext.dev/ar/docs/configuration.md#رؤوس-الصفحات-وتذييلاتها)
- [أنماط الفقرات](https://postext.dev/ar/docs/configuration.md#أنماط-الفقرات)
- [الترويسات بحسب دور الصفحة](https://postext.dev/ar/docs/configuration.md#عناصر-النص)
- [لوحة ألوان دلالية](https://postext.dev/ar/docs/configuration.md#لوحة-الألوان)
- [هوامش متناظرة](https://postext.dev/ar/docs/configuration.md#الهوامش-المتناظرة)
- [صفحات على اللوحة (Canvas)](https://postext.dev/ar/docs/configuration.md#رسم-صفحة-في-صورة-نقطية)
- [هندسة القسم](https://postext.dev/ar/docs/configuration.md#أنماط-العناوين)
- [ترويسات لكل قسم](https://postext.dev/ar/docs/configuration.md#أنماط-العناوين)
- [فصول بلا أرقام](https://postext.dev/ar/docs/configuration.md#أنماط-العناوين)

**الإعدادات في لمحة**

- [`bodyText`](https://postext.dev/ar/docs/configuration.md#نص-المتن), [`colorPalette`](https://postext.dev/ar/docs/configuration.md#لوحة-الألوان), `folio`, [`footer`](https://postext.dev/ar/docs/configuration.md#رؤوس-الصفحات-وتذييلاتها), [`header`](https://postext.dev/ar/docs/configuration.md#رؤوس-الصفحات-وتذييلاتها), [`headingStyles`](https://postext.dev/ar/docs/configuration.md#أنماط-العناوين), [`headings`](https://postext.dev/ar/docs/configuration.md#العناوين), [`layout`](https://postext.dev/ar/docs/configuration.md#التخطيط), [`locale`](https://postext.dev/ar/docs/configuration.md#تقسيم-الكلمات-بالواصلة), [`page`](https://postext.dev/ar/docs/configuration.md#الصفحة), [`paragraphStyles`](https://postext.dev/ar/docs/configuration.md#أنماط-الفقرات)

**واجهات API**

- [`buildDocument`](https://postext.dev/ar/docs/configuration.md#بناء-مستند), [`clearMeasurementCache`](https://postext.dev/ar/docs/configuration.md#ذاكرة-القياس-المؤقتة), [`createFolioFromDocument`](https://postext.dev/ar/docs/configuration.md#كتاب-ثلاثي-الأبعاد-postext-folio), [`renderPageToCanvas`](https://postext.dev/ar/docs/configuration.md#رسم-صفحة-في-صورة-نقطية)

**الخطوط**

- Newsreader (OFL-1.1), Bricolage Grotesque (OFL-1.1)

## طريقة التحضير

### 1 · سيّج الورقة الخارجية

```js
// script.js, سطرًا 137–220
// :::paper{type=cardStock grammage=250 shade=#f2c9b4}   pages 1–2: the front of the sheet
// # Low Tide {style="cover" …}   # In this issue {…}
// :::
// # The dunlin come back …   pages 3–6, outside any fence: folio.paper, the newsprint
// :::paper{type=cardStock grammage=250 shade=#f2c9b4}   pages 7–8: the back of the sheet
// # Join us {…}   # Low Tide {style="back" …}
// :::
const markdown = String.raw`---
title: "Low Tide"
subtitle: "A zine about the estuary"
author: "The Pill Estuary Group"
publishDate: "Issue 3 · Winter 2026"
---

:::paper{type=cardStock grammage=250 shade=#f2c9b4}

# Low Tide {style="cover" issue="Issue 3 · Winter 2026" strap="Mud, birds, boats and the people who watch them"}

# In this issue {style="feature" kicker="Welcome"}

Low Tide is made twice a year by the people who walk the river path at Pill. It is printed on the cheapest paper the printer has, folded by hand at the sailing club, and left in the pub, the post office and the bird hide. Take one. Leave it for somebody else when you have read it.

This winter we follow the dunlin that come back to the mud every October, talk to the last man in the village who still builds wooden boats, and print a tide table for the months when the light goes early. The cover is a heavier card than the pages inside it, so it survives a pocket and a little rain.

:::paragraphs{style="credits"}
**Edited by** Joan Avery and Sam Okonjo · **Drawings** Priya Lal · **Printed** in an edition of 400 copies, two sheets folded and stapled.
:::

:::

# The dunlin come back {style="feature" kicker="Birds"}

They arrive in the second week of October, a few dozen at first and then, within days, a thousand or more, feeding along the tideline in a moving grey band. Dunlin are small, about the size of a starling, with a long bill that droops at the tip. In winter they lose the black belly of their breeding plumage and turn the colour of the mud they feed on, which is why most people who walk the path never notice them until they lift.

When they lift, everybody notices. A flock of dunlin in the air turns as one bird: grey as it banks away from you, white as it turns back and shows its underside. On a clear afternoon at the top of the tide, when the mud is covered and the birds have nowhere left to feed, the flock rises off the saltmarsh and turns over the water for minutes at a time, flashing like a shoal of fish.

They come from the Arctic: from the tundra of northern Russia and Scandinavia, where they breed in the short summer and leave as soon as the young can fly. A dunlin ringed on the mud below the church in 2019 was caught again the following summer on the Taymyr Peninsula, five thousand kilometres away, and back on the same stretch of mud the October after.

Joan has counted them every Sunday morning for eleven winters. Her highest count was 4,200, in January 2021, after a week of frost on the moors drove birds down from the reservoirs. Her lowest was 380. She does not know why the numbers swing so much from year to year, and nor, she says, does anybody else.

## How to see them

Come two hours before high water and stand on the slipway by the sailing club. As the tide pushes in, the birds are driven up the mud towards you, closer and closer, until they give up and fly. Bring binoculars, and keep dogs on a lead below the hide: a bird that is flushed off the mud in winter spends energy it cannot afford.

# The last boatbuilder {style="feature" kicker="People"}

Dennis Harcombe built his first boat at fourteen, in his father's shed on Marine Parade, and his last one at seventy-eight. He reckons there were forty in between, give or take, most of them clinker-built dinghies of larch on oak, the kind every family on the river once kept at the bottom of the garden.

“The trick of it,” he says, “is that you never fight the wood. You look at a plank and you see which way it wants to bend, and you let it.” He still steams planks in a length of drainpipe over a camping stove, though these days only to repair other people's boats.

His shed is the last on the river where you can buy a pair of oars made to measure. He makes them from ash, shaves the looms by hand and charges what he charged in 1990, because, he says, the price was fair then and the oars have not got any better.

When he talks about the river he talks about the mud first: where it shifts, where the channel has moved since he was a boy, where a boat can sit out a tide and where it will be swallowed to the thwarts. He has watched the estuary change for seventy years and does not think it is finished.

# Tide table {style="feature" kicker="December to February"}

High water at Pill, in local time. Add about twenty minutes for the tide to reach the slipway at the sailing club. Spring tides, the biggest of the month, are marked in bold: on those days the mud below the hide is covered completely.

:::paragraphs{style="tides"}
**Sun 6 Dec** 07:42 and 20:05 · **Sat 12 Dec** 12:18 · **Sun 20 Dec** 06:55 and 19:20

**Sun 3 Jan** 06:31 and 18:54 · **Sat 9 Jan** 11:47 · **Sun 17 Jan** 06:10 and 18:36

**Sun 31 Jan** 05:58 and 18:22 · **Sat 6 Feb** 10:59 · **Sun 14 Feb** 05:37 and 18:01
:::

Times are for a still day. A strong westerly holds the water up and brings high tide a little early; a long dry spell on the moors does the opposite. If you are walking out on the mud, go on a falling tide and come back with an hour to spare.

:::paper{type=cardStock grammage=250 shade=#f2c9b4}

# Join us {style="back-matter" kicker="The group"}

We walk the river path on the first Sunday of every month, meeting at the sailing club at ten. Nobody needs to know anything about birds, boats or tides; most of us didn't when we started. Bring boots.

The next issue comes out in June. If you have something to say about the estuary, a drawing, a memory or a count of anything at all, leave it in the box behind the bar at the Duke of Cornwall, or write to us at the sailing club.

:::paragraphs{style="credits"}
Set in Newsreader and Bricolage Grotesque (SIL Open Font License) · Text: original, CC BY 4.0 · The Pill Estuary Group and the people in this issue are fictional.
:::

# Low Tide {style="back" strap="Free · take one, leave one"}

:::
`; // content.<lang>.md, inlined by the Cookbook
```

الكتيّب المدبّس على الطيّ رزمة من أوراق مطوية، فتحمل ورقة الغلاف الصفحتين الأوليين والصفحتين الأخيرتين. يحمل سياج الصفحتين 1–2، وسياج آخر الصفحتين 7–8، وتقع الصفحات بينهما خارج السياجين، على ورق الكتاب نفسه. يبدأ كل سياج وينتهي على صفحة جديدة، وهي هنا الصفحة التي يحتاجها الغلاف والترحيب على أي حال.

### 2 · ورق صحف، وتدبيس، وأغلفة من الصفحات

```js
// script.js, سطرًا 40–49
const folio = {
  tilt: 20,
  // The book's paper: every page outside a :::paper fence, the inner sheet here.
  paper: { type: 'newsprint', grammage: 52 },
  // cover: 'pages' turns page 1 as the front and page 8 (a verso) as the back; on a saddle
  // stitch the cover is a sheet like the others, only heavier (gotcha: folio-cover-pages-even).
  binding: { type: 'saddleStitch', cover: 'pages' },
  surface: { type: 'plain', color: col('ink') },
  lighting: { environment: 'overcast', intensity: 1.1 },
};
```

`folio.paper` هو ورق الصحف لكل صفحة خارج السياجين. يطوي `saddleStitch` الأوراق حول الدبابيس من غير كعب مسطح، ويجعل `cover: 'pages'` الصفحتين 1 و8 غلافين يُقلَبان. في التدبيس على الطيّ يُقلب الغلاف كما تُقلب الأوراق الأخرى، لكنه أصلب، وورقة الغلاف التي تسمّي ورقها الخاص (الكرتون) تُرسم بذلك الورق.

### 3 · غلاف بحبر واحد

```js
// script.js, سطرًا 53–86
// Eleven tide lines of a seeded length: blue rules across the foot of the cover.
let seed = 0x71de; // Mulberry32: the same lines on every run
const random = () => {
  seed = (seed + 0x6d2b79f5) | 0;
  let x = Math.imul(seed ^ (seed >>> 15), 1 | seed);
  x = (x + Math.imul(x ^ (x >>> 7), 61 | x)) ^ x;
  return ((x ^ (x >>> 14)) >>> 0) / 4294967296;
};
const tideLines = Array.from({ length: 11 }, (_, i) => ({ kind: 'rule', id: `tide-${i}`,
  direction: 'horizontal', thickness: pt(2.2 + i * 0.5), color: col('blue'),
  placement: { ...at('page', 'top-left', 10 + random() * 40, 128 + i * 5.2),
    size: { width: mm(50 + random() * 70) } } }));
const flush = { span: 'page', margins: { top: mm(0), bottom: mm(0), left: mm(0), right: mm(0) },
  footer: { elements: [] } }; // no folio on either cover (the style lasts one page)
const cover = { ...flush, id: 'cover', numbered: false, advancedDesign: { enabled: true,
  minHeight: mm(TRIM_H), slot: { elements: [
    text('issue', '{attr.issue}', caps(7.5), at('page', 'top-left', INNER, 16)),
    text('title', '{titleText}', { fontFamily: SANS, fontWeight: 800, fontSize: pt(70),
      lineHeight: 0.86, letterSpacing: pt(-2), color: col('blue') },
    { ...at('#issue', 'below', -1, 8), size: { width: mm(TRIM_W - INNER - OUTER) } }),
    text('strap', '{attr.strap}', { fontFamily: 'Newsreader', italic: true, fontSize: pt(13),
      lineHeight: 1.25, color: col('ink') },
    { ...at('#title', 'below', 1, 6), size: { width: mm(100) } }),
    ...tideLines,
  ] } } };
const back = { ...flush, id: 'back', numbered: false,
  breakBefore: { enabled: true, parity: 'even' }, advancedDesign: { enabled: true,
    minHeight: mm(TRIM_H), slot: { elements: [
      text('title', '{titleText}', { fontFamily: SANS, fontWeight: 800, fontSize: pt(26),
        letterSpacing: pt(-0.5), color: col('blue'), align: 'center' },
      { ...at('page', 'center', 0, -6), size: { width: mm(100) } }),
      text('strap', '{attr.strap}', { ...caps(7, col('ink')), align: 'center' },
        { ...at('#title', 'below', 0, 3), size: { width: mm(100) } }),
    ] } } };
```

الغلاف نمط عنوان بهوامش صفرية وتصميم بملء الصفحة: سطر العدد، والعنوان بخط Bricolage Grotesque بحجم 70 نقطة، والسطر التعريفي بخط Newsreader المائل، وأحد عشر خطًا للمد تأتي أطوالها من مولّد ببذرة ثابتة، فيرسم كل تشغيل الخطوط نفسها. لا شيء يطلي أرضية الصفحة: الكرتون يتولى ذلك.

### 4 · صفحة افتتاح واحدة لكل مقال

```js
// script.js, سطرًا 90–100
const feature = (id) => ({ id, numbered: false, breakBefore: { enabled: true, parity: 'any' },
  marginBottom: pt(LEAD), advancedDesign: { enabled: true, minHeight: mm(30), slot: {
    elements: [
      text('kicker', '{attr.kicker}', caps(7.5), at('container', 'top-left', 0, 1)),
      text('title', '{titleText}', { fontFamily: SANS, fontWeight: 800, fontSize: pt(25),
        lineHeight: 0.98, letterSpacing: pt(-0.4), color: col('ink') },
      { ...at('#kicker', 'below', 0, 2.5), size: { width: 'fill' } }),
      { kind: 'rule', id: 'rule', direction: 'horizontal', thickness: pt(2.5), color: col('blue'),
        placement: { ...at('#title', 'below', 0, 4), size: { width: mm(14) } } },
    ] } } });
const headingStyles = [cover, back, feature('feature'), feature('back-matter')];
```

كل مقال عنوان من المستوى الأول بنمط `feature`: صفحة جديدة، وعنوان تمهيدي من سماته، والعنوان، وخط أزرق قصير. وتمرير المقالات الخمسة عبر نمط واحد يُبقي داخل المجلة على خطين ولون مميِّز واحد.

### 5 · افتحها مدبّسة

الشيفرة هي [الجواب المختصر](#الجواب-المختصر) أعلاه. تقرأ `createFolioFromDocument` التجليد والأغلفة من `doc.config.folio`، وورق كل ورقة من `page.paper`. وينبّه المثال حين يكون عدد الصفحات فرديًا، لأن الصفحة 8 لا تكون غلافًا خلفيًا إلا إذا وقعت على صفحة زوجية.

## الوصفة كاملة

ملف واحد، مركّب من مجلد الوصفة ومعه نص المثال وأدوات دليل الوصفات المشتركة مضمّنة؛ وهو ينشئ صفحته بنفسه. لتشغيله، ضعه داخل `<script type="module">` في صفحة فارغة أو الصقه في لوحة JS في مثال CodePen جديد (وحدةً). يستورد postext من esm.sh، فلا حاجة إلى تثبيت أو بناء.

- مجلد الوصفة: https://github.com/drnachio/postext/tree/main/cookbook/saddle-stitched-card-cover

### script.js

```js
// ═══ Postext Cookbook · Nº 102 · A stapled zine with a card cover sheet ═══════════════
// https://postext.dev/en/cookbook/saddle-stitched-card-cover
// Code: MIT · Text: original (CC BY 4.0) · Pictures: drawn in code
// Fonts: Newsreader, Bricolage Grotesque (SIL OFL 1.1) · Needs postext ≥ 1.13.4
//
// Eight pages, two sheets folded and stapled through the fold. The outer sheet (pages 1–2 and
// 7–8) is salmon card, the inner sheet newsprint: two :::paper fences put the card on the
// cover pages, and postext-folio draws the book saddle-stitched, closed until you turn the
// cover. The flat pages below print on white: only the 3D book knows about paper.
import { buildDocument, renderPageToCanvas, clearMeasurementCache } from 'https://esm.sh/postext';
import { createFolioFromDocument } from 'https://esm.sh/postext-folio';

const LANG = 'en'; // @lang: the language of the sample document ('en' | 'es')
const RECIPE = 'saddle-stitched-card-cover';

// ─── 1 · Design ─────────────────────────────────────────────────────────────
// #region palette: two inks of a risograph, and the greys of the newsprint text
const palette = {
  ink: '#1f2124', // the text
  blue: '#0b6fb3', // the riso blue: titles, the tide lines, kickers
  muted: '#5d6066', // credits and folios
  rule: '#b9bcc0',
};
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' } })),
  { id: 'main-color', name: 'accent (defaults)', value: { hex: palette.blue, model: 'hex' } },
];
// #endregion
const [TRIM_W, TRIM_H, TOP, BOTTOM, INNER, OUTER] = [140, 200, 20, 22, 17, 15]; // mm
const LEAD = 13.6; // pt
const SANS = 'Bricolage Grotesque';
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, overflow: 'wrap',
  align: 'left', placement, ...look });
const caps = (size, color = col('blue')) => ({ fontFamily: SANS, fontSize: pt(size),
  fontWeight: 700, letterSpacing: pt(size * 0.18), textTransform: 'uppercase', color });

// #region folio: newsprint inside, stapled through the fold, covers taken from the pages
const folio = {
  tilt: 20,
  // The book's paper: every page outside a :::paper fence, the inner sheet here.
  paper: { type: 'newsprint', grammage: 52 },
  // cover: 'pages' turns page 1 as the front and page 8 (a verso) as the back; on a saddle
  // stitch the cover is a sheet like the others, only heavier (gotcha: folio-cover-pages-even).
  binding: { type: 'saddleStitch', cover: 'pages' },
  surface: { type: 'plain', color: col('ink') },
  lighting: { environment: 'overcast', intensity: 1.1 },
};
// #endregion

// #region covers: big type and tide lines on the page; the card colour comes from the paper
// Eleven tide lines of a seeded length: blue rules across the foot of the cover.
let seed = 0x71de; // Mulberry32: the same lines on every run
const random = () => {
  seed = (seed + 0x6d2b79f5) | 0;
  let x = Math.imul(seed ^ (seed >>> 15), 1 | seed);
  x = (x + Math.imul(x ^ (x >>> 7), 61 | x)) ^ x;
  return ((x ^ (x >>> 14)) >>> 0) / 4294967296;
};
const tideLines = Array.from({ length: 11 }, (_, i) => ({ kind: 'rule', id: `tide-${i}`,
  direction: 'horizontal', thickness: pt(2.2 + i * 0.5), color: col('blue'),
  placement: { ...at('page', 'top-left', 10 + random() * 40, 128 + i * 5.2),
    size: { width: mm(50 + random() * 70) } } }));
const flush = { span: 'page', margins: { top: mm(0), bottom: mm(0), left: mm(0), right: mm(0) },
  footer: { elements: [] } }; // no folio on either cover (the style lasts one page)
const cover = { ...flush, id: 'cover', numbered: false, advancedDesign: { enabled: true,
  minHeight: mm(TRIM_H), slot: { elements: [
    text('issue', '{attr.issue}', caps(7.5), at('page', 'top-left', INNER, 16)),
    text('title', '{titleText}', { fontFamily: SANS, fontWeight: 800, fontSize: pt(70),
      lineHeight: 0.86, letterSpacing: pt(-2), color: col('blue') },
    { ...at('#issue', 'below', -1, 8), size: { width: mm(TRIM_W - INNER - OUTER) } }),
    text('strap', '{attr.strap}', { fontFamily: 'Newsreader', italic: true, fontSize: pt(13),
      lineHeight: 1.25, color: col('ink') },
    { ...at('#title', 'below', 1, 6), size: { width: mm(100) } }),
    ...tideLines,
  ] } } };
const back = { ...flush, id: 'back', numbered: false,
  breakBefore: { enabled: true, parity: 'even' }, advancedDesign: { enabled: true,
    minHeight: mm(TRIM_H), slot: { elements: [
      text('title', '{titleText}', { fontFamily: SANS, fontWeight: 800, fontSize: pt(26),
        letterSpacing: pt(-0.5), color: col('blue'), align: 'center' },
      { ...at('page', 'center', 0, -6), size: { width: mm(100) } }),
      text('strap', '{attr.strap}', { ...caps(7, col('ink')), align: 'center' },
        { ...at('#title', 'below', 0, 3), size: { width: mm(100) } }),
    ] } } };
// #endregion

// #region features: every piece opens a page with a kicker and a title in the riso blue
const feature = (id) => ({ id, numbered: false, breakBefore: { enabled: true, parity: 'any' },
  marginBottom: pt(LEAD), advancedDesign: { enabled: true, minHeight: mm(30), slot: {
    elements: [
      text('kicker', '{attr.kicker}', caps(7.5), at('container', 'top-left', 0, 1)),
      text('title', '{titleText}', { fontFamily: SANS, fontWeight: 800, fontSize: pt(25),
        lineHeight: 0.98, letterSpacing: pt(-0.4), color: col('ink') },
      { ...at('#kicker', 'below', 0, 2.5), size: { width: 'fill' } }),
      { kind: 'rule', id: 'rule', direction: 'horizontal', thickness: pt(2.5), color: col('blue'),
        placement: { ...at('#title', 'below', 0, 4), size: { width: mm(14) } } },
    ] } } });
const headingStyles = [cover, back, feature('feature'), feature('back-matter')];
// #endregion

const config = () => ({ // a factory: the engine caches resolved configs per object
  locale: 'en-us',
  colorPalette,
  page: { sizePreset: 'custom', width: mm(TRIM_W), height: mm(TRIM_H), dpi: 150,
    margins: { top: mm(TOP), bottom: mm(BOTTOM), left: mm(INNER), right: mm(OUTER),
      mirror: true } },
  layout: { layoutType: 'single' },
  bodyText: { fontFamily: 'Newsreader', fontSize: pt(10), lineHeight: pt(LEAD),
    color: col('ink'), boldColor: col('ink'), italicColor: col('ink'),
    referenceColor: col('ink'), textAlign: 'justify', firstLineIndent: mm(4),
    indentAfterHeading: false, hyphenation: { enabled: true }, optimalLineBreaking: true,
    avoidWidows: true, avoidOrphans: true, avoidRunts: true },
  headings: { fontFamily: SANS, color: col('ink'), fontWeight: 800, levels: [
    // Restated: any headings object drops the H1 break (gotcha: headings-drop-h1-break).
    { level: 1, fontSize: pt(25), breakBefore: { enabled: true, parity: 'any' } },
    { level: 2, fontSize: pt(10), lineHeight: pt(LEAD), fontWeight: 700, color: col('blue'),
      marginTop: pt(LEAD), marginBottom: pt(0) },
  ] },
  headingStyles,
  paragraphStyles: [
    { id: 'credits', fontFamily: SANS, fontSize: pt(7.4), lineHeight: pt(10.4),
      color: col('muted'), boldColor: col('blue'), textAlign: 'left', firstLineIndent: pt(0),
      marginTop: pt(LEAD) },
    { id: 'tides', fontFamily: SANS, fontSize: pt(8.6), lineHeight: pt(LEAD),
      boldColor: col('blue'), textAlign: 'left', firstLineIndent: pt(0) },
  ],
  header: { elements: [] },
  footer: { elements: [text('folio', '{pageNumber}   {title} · {publishDate}', { ...caps(6.5,
    col('muted')), align: 'center' }, at('page', 'bottom', 0, -11))] },
  folio,
});

// ─── 2 · Content ────────────────────────────────────────────────────────────
// #region cover-sheet: the outer sheet's four pages in card, the inner sheet on newsprint
// :::paper{type=cardStock grammage=250 shade=#f2c9b4}   pages 1–2: the front of the sheet
// # Low Tide {style="cover" …}   # In this issue {…}
// :::
// # The dunlin come back …   pages 3–6, outside any fence: folio.paper, the newsprint
// :::paper{type=cardStock grammage=250 shade=#f2c9b4}   pages 7–8: the back of the sheet
// # Join us {…}   # Low Tide {style="back" …}
// :::
const markdown = String.raw`---
title: "Low Tide"
subtitle: "A zine about the estuary"
author: "The Pill Estuary Group"
publishDate: "Issue 3 · Winter 2026"
---

:::paper{type=cardStock grammage=250 shade=#f2c9b4}

# Low Tide {style="cover" issue="Issue 3 · Winter 2026" strap="Mud, birds, boats and the people who watch them"}

# In this issue {style="feature" kicker="Welcome"}

Low Tide is made twice a year by the people who walk the river path at Pill. It is printed on the cheapest paper the printer has, folded by hand at the sailing club, and left in the pub, the post office and the bird hide. Take one. Leave it for somebody else when you have read it.

This winter we follow the dunlin that come back to the mud every October, talk to the last man in the village who still builds wooden boats, and print a tide table for the months when the light goes early. The cover is a heavier card than the pages inside it, so it survives a pocket and a little rain.

:::paragraphs{style="credits"}
**Edited by** Joan Avery and Sam Okonjo · **Drawings** Priya Lal · **Printed** in an edition of 400 copies, two sheets folded and stapled.
:::

:::

# The dunlin come back {style="feature" kicker="Birds"}

They arrive in the second week of October, a few dozen at first and then, within days, a thousand or more, feeding along the tideline in a moving grey band. Dunlin are small, about the size of a starling, with a long bill that droops at the tip. In winter they lose the black belly of their breeding plumage and turn the colour of the mud they feed on, which is why most people who walk the path never notice them until they lift.

When they lift, everybody notices. A flock of dunlin in the air turns as one bird: grey as it banks away from you, white as it turns back and shows its underside. On a clear afternoon at the top of the tide, when the mud is covered and the birds have nowhere left to feed, the flock rises off the saltmarsh and turns over the water for minutes at a time, flashing like a shoal of fish.

They come from the Arctic: from the tundra of northern Russia and Scandinavia, where they breed in the short summer and leave as soon as the young can fly. A dunlin ringed on the mud below the church in 2019 was caught again the following summer on the Taymyr Peninsula, five thousand kilometres away, and back on the same stretch of mud the October after.

Joan has counted them every Sunday morning for eleven winters. Her highest count was 4,200, in January 2021, after a week of frost on the moors drove birds down from the reservoirs. Her lowest was 380. She does not know why the numbers swing so much from year to year, and nor, she says, does anybody else.

## How to see them

Come two hours before high water and stand on the slipway by the sailing club. As the tide pushes in, the birds are driven up the mud towards you, closer and closer, until they give up and fly. Bring binoculars, and keep dogs on a lead below the hide: a bird that is flushed off the mud in winter spends energy it cannot afford.

# The last boatbuilder {style="feature" kicker="People"}

Dennis Harcombe built his first boat at fourteen, in his father's shed on Marine Parade, and his last one at seventy-eight. He reckons there were forty in between, give or take, most of them clinker-built dinghies of larch on oak, the kind every family on the river once kept at the bottom of the garden.

“The trick of it,” he says, “is that you never fight the wood. You look at a plank and you see which way it wants to bend, and you let it.” He still steams planks in a length of drainpipe over a camping stove, though these days only to repair other people's boats.

His shed is the last on the river where you can buy a pair of oars made to measure. He makes them from ash, shaves the looms by hand and charges what he charged in 1990, because, he says, the price was fair then and the oars have not got any better.

When he talks about the river he talks about the mud first: where it shifts, where the channel has moved since he was a boy, where a boat can sit out a tide and where it will be swallowed to the thwarts. He has watched the estuary change for seventy years and does not think it is finished.

# Tide table {style="feature" kicker="December to February"}

High water at Pill, in local time. Add about twenty minutes for the tide to reach the slipway at the sailing club. Spring tides, the biggest of the month, are marked in bold: on those days the mud below the hide is covered completely.

:::paragraphs{style="tides"}
**Sun 6 Dec** 07:42 and 20:05 · **Sat 12 Dec** 12:18 · **Sun 20 Dec** 06:55 and 19:20

**Sun 3 Jan** 06:31 and 18:54 · **Sat 9 Jan** 11:47 · **Sun 17 Jan** 06:10 and 18:36

**Sun 31 Jan** 05:58 and 18:22 · **Sat 6 Feb** 10:59 · **Sun 14 Feb** 05:37 and 18:01
:::

Times are for a still day. A strong westerly holds the water up and brings high tide a little early; a long dry spell on the moors does the opposite. If you are walking out on the mud, go on a falling tide and come back with an hour to spare.

:::paper{type=cardStock grammage=250 shade=#f2c9b4}

# Join us {style="back-matter" kicker="The group"}

We walk the river path on the first Sunday of every month, meeting at the sailing club at ten. Nobody needs to know anything about birds, boats or tides; most of us didn't when we started. Bring boots.

The next issue comes out in June. If you have something to say about the estuary, a drawing, a memory or a count of anything at all, leave it in the box behind the bar at the Duke of Cornwall, or write to us at the sailing club.

:::paragraphs{style="credits"}
Set in Newsreader and Bricolage Grotesque (SIL Open Font License) · Text: original, CC BY 4.0 · The Pill Estuary Group and the people in this issue are fictional.
:::

# Low Tide {style="back" strap="Free · take one, leave one"}

:::
`; // content.<lang>.md, inlined by the Cookbook
// #endregion

// ─── 3 · Fonts ──────────────────────────────────────────────────────────────
const FONTS = {
  Newsreader: ['400', '400i', '700'],
  'Bricolage Grotesque': ['400', '700', '800'],
};

// ─── 4 · Build & show ───────────────────────────────────────────────────────
await loadFonts(FONTS, markdown);
const doc = await buildWithFonts(() => buildDocument({ markdown }, config()), markdown);
showPages(doc, { title: 'Low Tide · a stapled zine in Folio' });

// #region answer: the zine as a stapled booklet, closed on the desk until the cover turns
const stage = document.createElement('section');
stage.id = 'folio';
stage.ariaLabel = 'The zine in 3D';
stage.style.cssText = 'height: min(78vh, 720px); margin: 0 auto; max-width: 1280px';
document.getElementById('pages').before(stage);
// Covers from the pages need an even count: page 8 must fall on a verso to be the back.
if (doc.pages.length % 2) console.warn(`${doc.pages.length} pages: the back cover is missing`);
createFolioFromDocument(stage, doc, {
  appearance: { textureBaseUrl: 'https://postext.dev/folio/textures' },
  onChange: ({ pages }) => kitStatus(pages.map((i) => `p. ${i + 1} on `
    + (doc.pages[i].paper ? 'card' : 'newsprint')).join(' · ')),
});
// #endregion

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

## تنويعات

### غلاف أبيض أثقل

غيّر السياجين فتصير ورقة الغلاف كرتونًا أبيض بوزن 300 غرام؛ وتبقى الصفحات الداخلية على ورق الصحف.

```diff
-:::paper{type=cardStock grammage=250 shade=#f2c9b4}
+:::paper{type=cardStock grammage=300 shade=#fbfaf6}
```

### كتيّب بسيط

احذف السياجين فيصير الغلاف ورقة من ورق الصحف نفسه الذي في الصفحات، أثقل قليلًا. أما [كتاب عيّنات الورق](https://postext.dev/ar/cookbook/paper-swatch-book.md) فيسلك الطريق المعاكس ويعطي كل ورقة ورقها الخاص.

## أخطاء شائعة

- **Cover: 'pages' يحتاج إلى عدد زوجي من الصفحات للغلاف الخلفي.** حين يُضبط binding.cover على 'pages' تنقلب الصفحة الأولى غلافًا أماميًا، ولا تنقلب الصفحة الأخيرة غلافًا خلفيًا إلا إذا وقعت في صفحة زوجية. فالعدد الفردي من الصفحات يترك الكتاب بغلاف أمامي بلا غلاف خلفي: أكمله إلى عدد زوجي أو احتفظ بالغلاف الصلب.
- **مقطع الورق يبدأ وينتهي في صفحة جديدة، والورقة تأخذ ورق صفحتها الفردية.** النص داخل :::paper يبدأ في صفحة جديدة، والنص الذي بعده كذلك. للورقة صفحتان وورق واحد، هو ورق صفحتها الفردية، فالمقطع الذي يبدأ في صفحة زوجية يطبع تلك الصفحة على ورق الورقة التي تشاركها. ضع فاصل صفحة حيث يجب أن يبدأ المقطع في صفحة فردية.
- **يأخذ عارض Folio مقاس حاويته.** يضع createFolioFromDocument الكتاب داخل العنصر الذي تعطيه إياه، مع الأزرار وعدد الصفحات في هوامشه. والعنصر الذي لا ارتفاع له (div فارغ في التدفق العادي) لا يعرض شيئًا: أعطه ارتفاعًا في CSS.
- **حمّل كل أوجه الخط قبل الإخراج.** يقيس الإخراج النص بأوجه الخط التي حمّلها المتصفح ويخزّن العروض مؤقتًا، فالوجه الذي يصل بعد البناء الأول يترك فواصل أسطر خاطئة وملف PDF لم يعد يطابق الشاشة. حمّل كل وزن وكل نمط أولًا، واستدعِ clearMeasurementCache() قبل إعادة البناء إذا تأخر وصول أحدها.
- **يُخزَّن الإعداد مؤقتًا بحسب هويته: ابنِ كائنًا جديدًا.** يخزّن المحرّك الإعدادات المحسوبة مؤقتًا بحسب هوية الكائن، فتعديل الإعداد في مكانه ثم البناء مجددًا يعيد استخدام النتيجة القديمة. ابنِ كائنًا جديدًا في كل بناء، ولهذا يكون إعداد الوصفة دالة مصنِّعة: config().

## الحقوق

- الوصفة: Ignacio Ferro ([@drnachio](https://github.com/drnachio))
- الصور: The tide lines on the cover, drawn in code: Ignacio Ferro, CC-BY-4.0
- الخطوط: Newsreader (OFL-1.1), Bricolage Grotesque (OFL-1.1)
- الشيفرة: MIT · محتوى المثال: CC-BY-4.0

## وصفات ذات صلة

- [رقم 100 · كتاب مُخرَج على المكتب، بالأبعاد الثلاثة](https://postext.dev/ar/cookbook/folio-book-on-a-desk.md): تفتح الدالة createFolioFromDocument من الحزمة postext-folio فصلًا مُخرَجًا ككتاب مجلَّد بالقماش على مكتب من خشب الجوز، بمظهر يحدده config.folio. · المستوى 2 (متوسط) · الأدب القصصي والمسرح والنثر الأدبي
- [رقم 051 · مجلة ريزو بحبرين: الرسوم بلون خاص واحد](https://postext.dev/ar/cookbook/riso-zine-single-ink.md): مجلة من أربع صفحات لآلة ريزوغراف بأسطوانتين: الرسوم الملونة تُطبع درجاتٍ من الوردي، يعيد المثال تلوينها لـCanvas وrenderToPdf في PDF. · المستوى 3 (متقدم) · المجلات والمجلات المستقلة
- [رقم 011 · مصدر واحد وطبعتان للورق والشاشة](https://postext.dev/ar/cookbook/print-and-screen-editions.md): الطبعتان من إعداد واحد: يحمل htmlViewer.overrides تصميم الشاشة الداكن، ويدمجه applyHtmlViewerOverrides قبل كل بناء HTML. · المستوى 3 (متقدم) · المجلات والمجلات المستقلة
