# Un assaig d'humanitats amb obres citades en MLA

> Un assaig literari amb cites [@clau, 48] que surten en MLA 9, autor i pàgina, amb una cita en bloc i la llista d'obres citades presa de dades CSL-YAML.

- Versió HTML: https://postext.dev/ca/cookbook/mla-humanities-essay
- Recepta Núm. 096 · Estructura del llibre · Nivell 2 (Intermedi) · Sortides: Canvas, PDF
- Gèneres: Articles i treballs acadèmics
- Requereix postext ≥ 1.12.1, postext-pdf ≥ 1.12.1 · provada amb 1.12.1, postext-pdf 1.12.1 el 2026-10-01
- Pàgines: [1](https://postext.dev/cookbook/mla-humanities-essay/es/p01.webp?v=129c8802), [2](https://postext.dev/cookbook/mla-humanities-essay/es/p02.webp?v=129c8802), [3](https://postext.dev/cookbook/mla-humanities-essay/es/p03.webp?v=129c8802), [4](https://postext.dev/cookbook/mla-humanities-essay/es/p04.webp?v=129c8802)
- PDF: https://postext.dev/cookbook/mla-humanities-essay/es/mla-humanities-essay.pdf?v=129c8802
- Obre al Sandbox: https://postext.dev/ca/sandbox#recipe=mla-humanities-essay&lang=es (.postext: https://postext.dev/cookbook/mla-humanities-essay/es/mla-humanities-essay.postext)
- Última actualització: 2026-10-01
- Altres idiomes: [en](https://postext.dev/en/cookbook/mla-humanities-essay.md), [es](https://postext.dev/es/cookbook/mla-humanities-essay.md), [zh](https://postext.dev/zh/cookbook/mla-humanities-essay.md), [ar](https://postext.dev/ar/cookbook/mla-humanities-essay.md)

## En poques paraules

Un assaig sobre un poema de Keats, tal com l'imprimeix una revista literària. L'autora escriu una clau curta per a cada font; Postext imprimeix l'autor i la pàgina entre parèntesis i la llista d'obres citades al final, en estil MLA.

## Què compondràs

Un assaig d'una revista literària trimestral: quatre pàgines sobre qui diu els dos últims versos de l'«Ode on a Grecian Urn» de Keats. La pàgina fa 150 × 230 mm i va en Spectral, amb els títols de secció i les capçaleres en Spectral SC; l'obertura ocupa una banda del negre del vernís sobre una franja d'argila vermella, els dos colors d'un vas àtic. Les cites segueixen MLA 9, l'estil de gairebé tots els departaments de literatura i d'història de l'art: autor i pàgina entre parèntesis, només la pàgina quan la frase ja anomena l'autor, un títol abreujat quan dues obres són del mateix autor i la llista d'obres citades en pàgina a part. Cada cita és una clau en el text i l'estil és un sol paràmetre de configuració.

**Aquesta recepta respon a:**

- Com cito en MLA i munto la llista d'obres citades d'un assaig de literatura?

## La resposta curta

```js
// script.js, línies 32–44
// The text cites with [@keats-letters, 48] → (Keats, Letters 48): MLA prints the author
// and the page, and adds a short title when two works share an author. [-@eliot1932, 230]
// drops the name the sentence already gives → (230). The list is alphabetical, and a
// second work by the same author opens with a dash (MLA's ---) instead of the name.
registerCitationEngine(createCiteprocEngine({ styles: STYLES, locales: LOCALES }));
const citations = {
  style: 'modern-language-association', // MLA 9th edition, author-page
  link: true, // a citation jumps to its entry in Works Cited
  bibliography: {
    // MLA's half-inch hanging indent, scaled to a 108 mm measure.
    fontSize: em(0.94), lineHeight: pt(13.4), hangingIndent: em(2.2), entrySpacing: pt(3),
  },
};
```

## Ingredients

**Ensenya**

- [Cites en un estil de cita](https://postext.dev/ca/docs/document-format.md#citacions-i-bibliografia): Obres citades com a [@clau, pàg. 33] i formatades en un estil CSL (APA, Chicago, MLA, IEEE, Vancouver, ISO 690, GB/T 7714…) triat a la configuració, enllaçades amb les seves entrades.
- [Bibliografia a partir de les referències](https://postext.dev/ca/docs/document-format.md#citacions-i-bibliografia): La llista d'obres citades, construïda a partir de les referències del document (front matter o un bloc BibTeX) on hi ha :::bibliography o després de l'últim capítol.

**També fa servir**

- [Obertures dissenyades](https://postext.dev/ca/docs/configuration.md#span-i-disseny-avançat)
- [Atributs de títol](https://postext.dev/ca/docs/document-format.md#atributs-dencapçalament)
- [Estils de títol](https://postext.dev/ca/docs/configuration.md#estils-dencapçalament)
- [Capçaleres i folis](https://postext.dev/ca/docs/configuration.md#capçaleres-i-peus)
- [Marges simètrics](https://postext.dev/ca/docs/configuration.md#marges-simètrics-mirall)
- [Estils de paràgraf](https://postext.dev/ca/docs/configuration.md#estils-de-paràgraf)
- [Paleta de color semàntica](https://postext.dev/ca/docs/configuration.md#paleta-de-colors)
- [Exportació a PDF](https://postext.dev/ca/docs/configuration.md#generació-de-pdf)
- [Referències creuades](https://postext.dev/ca/docs/document-format.md#referències-creuades-i-àncores)
- [Color del paper](https://postext.dev/ca/docs/configuration.md#pàgina)
- [Capçaleres segons el tipus de pàgina](https://postext.dev/ca/docs/configuration.md#elements-de-text)
- [Fonts incrustades al PDF](https://postext.dev/ca/docs/configuration.md#per-què-un-proveïdor-de-fonts)
- [Espai vertical explícit](https://postext.dev/ca/docs/document-format.md#space)

**La configuració d'un cop d'ull**

- [`bodyText`](https://postext.dev/ca/docs/configuration.md#text-de-cos), [`citations`](https://postext.dev/ca/docs/configuration.md#cites), [`colorPalette`](https://postext.dev/ca/docs/configuration.md#paleta-de-colors), [`footer`](https://postext.dev/ca/docs/configuration.md#capçaleres-i-peus), [`header`](https://postext.dev/ca/docs/configuration.md#capçaleres-i-peus), [`headingStyles`](https://postext.dev/ca/docs/configuration.md#estils-dencapçalament), [`headings`](https://postext.dev/ca/docs/configuration.md#encapçalaments), [`layout`](https://postext.dev/ca/docs/configuration.md#disposició), [`locale`](https://postext.dev/ca/docs/configuration.md#partició-de-mots), [`page`](https://postext.dev/ca/docs/configuration.md#pàgina), [`paragraphStyles`](https://postext.dev/ca/docs/configuration.md#estils-de-paràgraf)

**API**

- [`LOCALES`](https://postext.dev/ca/docs/document-format.md#citacions-i-bibliografia), [`STYLES`](https://postext.dev/ca/docs/document-format.md#citacions-i-bibliografia), [`buildDocument`](https://postext.dev/ca/docs/configuration.md#construir-un-document), [`clearMeasurementCache`](https://postext.dev/ca/docs/configuration.md#memòria-cau-de-mesures), [`createCiteprocEngine`](https://postext.dev/ca/docs/document-format.md#citacions-i-bibliografia), [`decompressWoff2`](https://postext.dev/ca/docs/configuration.md#proveïdor-de-fonts-al-navegador-fontsource--woff2), [`registerCitationEngine`](https://postext.dev/ca/docs/document-format.md#citacions-i-bibliografia), [`renderPageToCanvas`](https://postext.dev/ca/docs/configuration.md#renderitzar-una-pàgina-a-un-bitmap), [`renderToPdf`](https://postext.dev/ca/docs/configuration.md#generació-de-pdf)

**Tipus de lletra**

- Spectral (OFL-1.1), Spectral SC (OFL-1.1)

## Elaboració

### 1 · L'estil és MLA; les dades, CSL

El codi és [la resposta curta](#la-resposta-curta) de més amunt. El motor de cites es registra una vegada i `citations.style` anomena l'estil MLA inclòs; citeproc-js hi redacta cada cita i la llista. Les referències van en un bloc `:::references{format=csl-yaml}` al final de l'assaig. Aquí es fa servir CSL-YAML i no BibTeX perquè MLA necessita un títol abreujat per a les dues obres de Keats, i el lector de BibTeX de postext 1.12.1 descarta el camp `shorttitle`; en CSL el camp es diu `title-short`.

### 2 · Tres maneres de citar

El text cita una obra amb `[@jack1967]`, que imprimeix «(Jack)» perquè l'assaig es refereix al llibre sencer. Amb pàgina, `[@keats-letters, 41]` imprimeix «(Keats, *Letters* 41)»: se citen dues obres de Keats, així que citeproc hi afegeix el títol abreujat per distingir-les. Quan la frase ja anomena el crític, `[-@eliot1932, 230]` omet el nom i imprimeix «(230)». Un cop citada l'oda amb els seus números de vers, les cites següents porten només els números, escrits entre parèntesis normals, com permet MLA.

### 3 · Una cita en bloc amb la referència després del punt

```js
// script.js, línies 48–52
// More than four lines of prose go in a Markdown blockquote: in ink, indented, with no
// first-line indent. Its citation follows the final full stop, as MLA asks.
const blockquote = {
  color: col('ink'), italic: false, indent: em(2), firstLineIndent: em(0),
};
```

La carta sobre la Negative Capability passa de quatre línies de prosa, així que va a part com a cita en bloc de Markdown: en rodona, en el color del text, sagnada 2 em i sense sagnat de primera línia. MLA posa la referència d'una cita en bloc darrere del punt final, i l'original fa el mateix: `…fact and reason. [@keats-letters, 48]`. Un `:::space{lines=0.5}` a sobre i un altre a sota la separen dels paràgrafs que l'envolten.

### 4 · Les obres citades, en pàgina a part

```js
// script.js, línies 119–119
  headingStyles: [{ id: 'works-cited', breakBefore: { enabled: true, parity: 'any' } }],
```

El títol porta un estil que salta a pàgina nova, i `:::bibliography{title=""}` col·loca la llista a sota sense un segon títol. La llista va en ordre alfabètic amb sagnat francès, i la segona obra de Keats comença amb una ratlla triple en lloc del nom, com demana MLA per als autors repetits. Un foli al peu apareix en aquesta pàgina i a l'obertura, on no hi ha capçaleres.

### 5 · L'obertura en els dos colors d'un vas

```js
// script.js, línies 56–81
const BAND = 116; // mm from the trim's top
const opener = {
  enabled: true,
  minHeight: mm(BAND - MARGIN.top + 4), // the text starts a line under the band
  slot: { elements: [
    { kind: 'box', id: 'band', style: { backgroundColor: col('glaze') },
      placement: { ...at('page', 'top-left'), size: { width: 'fill', height: mm(BAND) } } },
    { kind: 'box', id: 'foot', style: { backgroundColor: col('clay') },
      placement: { ...at('page', 'top-left', 0, BAND - 3), size: { width: 'fill',
        height: mm(3) } } },
    { kind: 'text', id: 'kicker', content: '{attr.kicker}', fontFamily: LABEL, fontWeight: 600,
      fontSize: pt(9), letterSpacing: pt(1.6), color: col('slip'), align: 'left',
      placement: at('container', 'top-left', 0, 6) },
    { kind: 'text', id: 'title', content: '{titleText}', fontFamily: TEXT, fontWeight: 300,
      italic: true, fontSize: pt(56), lineHeight: 0.95, color: col('paper'), align: 'left',
      overflow: 'wrap', placement: { ...at('container', 'top-left', -1, 30),
        size: { width: mm(110), height: 'auto' } } },
    { kind: 'text', id: 'sub', content: '{attr.sub}', fontFamily: TEXT, fontWeight: 300,
      fontSize: pt(13), lineHeight: 1.25, color: col('paper'), align: 'left', overflow: 'wrap',
      placement: { ...at('container', 'top-left', 0, 66),
        size: { width: mm(96), height: 'auto' } } },
    { kind: 'text', id: 'byline', content: '{attr.byline}', fontFamily: LABEL, fontWeight: 600,
      fontSize: pt(9.5), letterSpacing: pt(1.2), color: col('slip'), align: 'left',
      placement: at('container', 'top-left', 0, 82) },
  ] },
};
```

```js
// script.js, línies 16–23
const palette = {
  ink: '#1f1a17', glaze: '#231c19', clay: '#9a3d22', slip: '#e3a27e', rule: '#c9bdb0',
  muted: '#6b6159', paper: '#fffdf9',
};
// A design element paints the hex written beside its paletteId (gotcha: palette-skips-designs).
const col = (id) => ({ hex: palette[id], model: 'hex', paletteId: id });
const colorPalette = Object.entries({ ...palette, 'main-color': palette.clay })
  .map(([id, hex]) => ({ id, name: id, value: { hex, model: 'hex' } }));
```

La banda fa 116 mm i porta l'avantítol, el títol de 56 pt en Spectral Light cursiva, el subtítol i la signatura, tots presos d'atributs del títol. La franja d'argila és una segona caixa ancorada 3 mm per sobre del peu de la banda.

## La recepta completa

Un sol fitxer, compost a partir de la carpeta de la recepta amb el text d'exemple i el kit comú del Receptari ja inclosos; construeix la seva pròpia pàgina. Per executar-lo, posa'l en un `<script type="module">` d'una pàgina buida o enganxa'l al tauler JS d'un pen nou de CodePen (com a mòdul). Importa postext des d'esm.sh, així que no cal instal·lar ni compilar res.

- Carpeta de la recepta: https://github.com/drnachio/postext/tree/main/cookbook/mla-humanities-essay

### script.js

```js
// ═══ Postext Cookbook · Nº 096 · A humanities essay with MLA works cited ══════════
// https://postext.dev/en/cookbook/mla-humanities-essay
// Code: MIT · Text: original (CC BY 4.0) · Pictures: none
// Fonts: Spectral, Spectral SC (SIL OFL 1.1) · Needs postext ≥ 1.12.1
import {
  buildDocument, renderPageToCanvas, clearMeasurementCache, registerCitationEngine,
} from 'https://esm.sh/postext';
import { renderToPdf, decompressWoff2 } from 'https://esm.sh/postext-pdf';
import { createCiteprocEngine, STYLES, LOCALES } from 'https://esm.sh/postext-citeproc';

const LANG = 'es'; // @lang: the language of the sample document ('en' | 'es')
const RECIPE = 'mla-humanities-essay';

// ─── 1 · Design ─────────────────────────────────────────────────────────────
// #region palette: the black glaze and the red clay of an Attic vase
const palette = {
  ink: '#1f1a17', glaze: '#231c19', clay: '#9a3d22', slip: '#e3a27e', rule: '#c9bdb0',
  muted: '#6b6159', paper: '#fffdf9',
};
// A design element paints the hex written beside its paletteId (gotcha: palette-skips-designs).
const col = (id) => ({ hex: palette[id], model: 'hex', paletteId: id });
const colorPalette = Object.entries({ ...palette, 'main-color': palette.clay })
  .map(([id, hex]) => ({ id, name: id, value: { hex, model: 'hex' } }));
// #endregion
const TEXT = 'Spectral', LABEL = 'Spectral SC';
const TRIM = { width: 150, height: 230 }; // a literary quarterly
const MARGIN = { top: 22, bottom: 22, inner: 19, outer: 21 };
const LEAD = 14.4; // pt
const at = (to, edge, x = 0, y = 0) => ({ anchor: { to, edge }, offset: { x: mm(x), y: mm(y) } });

// #region answer: MLA 9 author-page citations and a Works Cited list
// The text cites with [@keats-letters, 48] → (Keats, Letters 48): MLA prints the author
// and the page, and adds a short title when two works share an author. [-@eliot1932, 230]
// drops the name the sentence already gives → (230). The list is alphabetical, and a
// second work by the same author opens with a dash (MLA's ---) instead of the name.
registerCitationEngine(createCiteprocEngine({ styles: STYLES, locales: LOCALES }));
const citations = {
  style: 'modern-language-association', // MLA 9th edition, author-page
  link: true, // a citation jumps to its entry in Works Cited
  bibliography: {
    // MLA's half-inch hanging indent, scaled to a 108 mm measure.
    fontSize: em(0.94), lineHeight: pt(13.4), hangingIndent: em(2.2), entrySpacing: pt(3),
  },
};
// #endregion

// #region quotation: a block quotation, set in and upright
// More than four lines of prose go in a Markdown blockquote: in ink, indented, with no
// first-line indent. Its citation follows the final full stop, as MLA asks.
const blockquote = {
  color: col('ink'), italic: false, indent: em(2), firstLineIndent: em(0),
};
// #endregion

// #region opener: the title in a band of black glaze with a red clay foot
const BAND = 116; // mm from the trim's top
const opener = {
  enabled: true,
  minHeight: mm(BAND - MARGIN.top + 4), // the text starts a line under the band
  slot: { elements: [
    { kind: 'box', id: 'band', style: { backgroundColor: col('glaze') },
      placement: { ...at('page', 'top-left'), size: { width: 'fill', height: mm(BAND) } } },
    { kind: 'box', id: 'foot', style: { backgroundColor: col('clay') },
      placement: { ...at('page', 'top-left', 0, BAND - 3), size: { width: 'fill',
        height: mm(3) } } },
    { kind: 'text', id: 'kicker', content: '{attr.kicker}', fontFamily: LABEL, fontWeight: 600,
      fontSize: pt(9), letterSpacing: pt(1.6), color: col('slip'), align: 'left',
      placement: at('container', 'top-left', 0, 6) },
    { kind: 'text', id: 'title', content: '{titleText}', fontFamily: TEXT, fontWeight: 300,
      italic: true, fontSize: pt(56), lineHeight: 0.95, color: col('paper'), align: 'left',
      overflow: 'wrap', placement: { ...at('container', 'top-left', -1, 30),
        size: { width: mm(110), height: 'auto' } } },
    { kind: 'text', id: 'sub', content: '{attr.sub}', fontFamily: TEXT, fontWeight: 300,
      fontSize: pt(13), lineHeight: 1.25, color: col('paper'), align: 'left', overflow: 'wrap',
      placement: { ...at('container', 'top-left', 0, 66),
        size: { width: mm(96), height: 'auto' } } },
    { kind: 'text', id: 'byline', content: '{attr.byline}', fontFamily: LABEL, fontWeight: 600,
      fontSize: pt(9.5), letterSpacing: pt(1.2), color: col('slip'), align: 'left',
      placement: at('container', 'top-left', 0, 82) },
  ] },
};
// #endregion

const head = (id, content, parity, edge, x, align) => ({
  kind: 'text', id, content, parity, pages: 'body', fontFamily: LABEL, fontWeight: 500,
  fontSize: pt(8.5), letterSpacing: pt(1), color: col('muted'), align,
  placement: at('page', edge, x, 12),
});

const config = () => ({ // a factory: the engine caches resolved configs per object
  locale: t({ en: 'en-us', es: 'es' }), // exact codes (gotcha: hyphenation-locales)
  colorPalette,
  citations,
  page: {
    sizePreset: 'custom', 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 },
  },
  layout: { layoutType: 'single' },
  bodyText: {
    fontFamily: TEXT, fontSize: pt(10.6), lineHeight: pt(LEAD), color: col('ink'),
    boldColor: col('ink'), italicColor: col('ink'), referenceColor: col('ink'),
    textAlign: 'justify', firstLineIndent: mm(5), indentAfterHeading: false,
    hyphenation: { enabled: true }, optimalLineBreaking: true,
    avoidWidows: true, avoidOrphans: true, avoidRunts: true, blockquote,
  },
  headings: {
    fontFamily: LABEL, fontWeight: 600, color: col('clay'),
    levels: [
      // Restated: any headings object drops the H1 break (gotcha: headings-drop-h1-break).
      { level: 1, fontFamily: TEXT, fontSize: pt(56), marginBottom: pt(0),
        breakBefore: { enabled: true, parity: 'any' }, advancedDesign: opener },
      { level: 2, fontSize: pt(11.5), letterSpacing: pt(0.9), lineHeight: pt(LEAD),
        marginTop: pt(LEAD), marginBottom: pt(LEAD / 2) },
    ],
  },
  // #region works-cited: MLA starts the list on a page of its own
  headingStyles: [{ id: 'works-cited', breakBefore: { enabled: true, parity: 'any' } }],
  // #endregion
  paragraphStyles: [
    { id: 'colophon', fontFamily: LABEL, fontSize: pt(7.5), lineHeight: pt(10),
      color: col('muted'), textAlign: 'left', firstLineIndent: pt(0), marginTop: pt(LEAD * 2) },
  ],
  header: { elements: [
    head('verso-folio', '{pageNumber}', 'even', 'top-left', MARGIN.outer, 'left'),
    head('verso-author', '{attr.byline}', 'even', 'top-left', MARGIN.outer + 8, 'left'),
    head('recto-title', '{title}', 'odd', 'top-right', -(MARGIN.outer + 8), 'right'),
    head('recto-folio', '{pageNumber}', 'odd', 'top-right', -MARGIN.outer, 'right'),
  ] },
  footer: { elements: [ // a drop folio where the running heads stand down
    { ...head('drop-folio', '{pageNumber}', 'all', 'top', 0, 'center'),
      pages: 'opener', placement: at('container', 'top', 0, 9) },
  ] },
});

// ─── 2 · Content ────────────────────────────────────────────────────────────
const markdown = String.raw`---
title: "Fría pastoral"
author: "Helena Ward"
---

# Fría pastoral {#essay kicker="Ensayos · Poesía romántica" sub="Sobre la voz de los dos últimos versos de la «Ode on a Grecian Urn» de Keats" byline="Helena Ward"}

Ninguna urna griega se corresponde con la que describe Keats. Quienes fueron a buscarla al British Museum encontraron trozos de varias: la novilla que llevan al sacrificio en el friso del Partenón, que Keats vio entre los mármoles de Elgin en la primavera de 1817, y los danzantes báquicos del vaso Townley, junto con los del vaso Borghese del Louvre, que en Londres se conocía por grabados [@jack1967]. Lo más parecido a una fuente única es una hoja de papel. Keats calcó un grabado del vaso Sosibios, una crátera de mármol firmada por un escultor ateniense del siglo I a. C., a partir del libro de vasos antiguos de Henry Moses, y el calco se conserva [@mcdermott1948]. La urna del poema es una suma hecha con mármoles y con estampas de mármoles, y al final habla.

Keats escribió la oda en mayo de 1819. Se imprimió por primera vez en enero de 1820 en los *Annals of the Fine Arts*, una revista londinense de pintura y escultura, y ese verano el poeta la recogió en su tercer y último libro de poemas. Las dos versiones difieren en un signo de puntuación, y buena parte de lo que se ha escrito después sobre el poema gira en torno a ese signo.

## Las preguntas

La oda empieza nombrando tres veces a su objeto: «still unravish’d bride of quietness», «foster-child of silence and slow time» y «Sylvan historian» [@keats-ode, lines 1–3]. Los nombres son una cortesía, porque el hablante quiere que la historiadora le cuente su historia. «What men or gods are these? What maidens loth? / What mad pursuit? What struggle to escape?» (8–9). La urna no contesta, y durante tres estrofas el hablante contesta por ella: prefiere las melodías «unheard» de las flautas pintadas a las que se oyen (11–12) y celebra a un amante que nunca alcanzará a la muchacha pero siempre la amará.

La cuarta estrofa pasa de los danzantes a una procesión, un sacerdote que conduce una novilla «lowing at the skies» (33), y después a un pueblo que ni siquiera está en la urna, vaciado de su gente por el sacrificio y condenado al silencio «for evermore» (38). Aquí las preguntas dejan de tener respuesta posible. El pueblo existe solo porque la procesión tiene que haber salido de algún sitio.

## La puntuación de los últimos versos

En la última estrofa el hablante deja de preguntar. La urna «dost tease us out of thought / As doth eternity: Cold Pastoral!» (44–45) y sobrevivirá a esta generación como «a friend to man, to whom thou say’st, / “Beauty is truth, truth beauty,”—that is all / Ye know on earth, and all ye need to know» (48–50). En el libro de 1820 las comillas se cierran después de «beauty», de modo que la urna dice cinco palabras y el resto lo dice otro. El texto de los *Annals* no lleva comillas, y tampoco las llevan las copias que hicieron los amigos de Keats [@stillinger1974]. Un editor tiene que elegir, y la elección decide quién dice «that is all»: la urna a la humanidad, el poeta a su lector o el poeta a las figuras de la urna.

Los versos han tenido lectores hostiles. T. S. Eliot vio en ellos «a serious blemish on a beautiful poem» [-@eliot1932, 230], una afirmación falsa o que él no alcanzaba a entender. Cleanth Brooks le respondió que las palabras son «a speech ‘in character’» [-@brooks1947, 165], ajustadas a la urna que las pronuncia, y las comparó con el «Ripeness is all» de Edgar en *King Lear*. Su lectura necesita la puntuación de 1820: si la urna dice solo las cinco palabras entre comillas, el lema es de la urna, y el comentario que sigue a la raya pertenece al poeta que la ha escuchado.

## Las cartas

Keats había escrito sobre la belleza y la verdad antes de escribir sobre la urna. «What the Imagination seizes as Beauty must be truth—whether it existed before or not», le dijo a Benjamin Bailey en noviembre de 1817 [@keats-letters, 41]. Un mes después, de vuelta de la pantomima de Navidad, dio nombre a la cualidad que a su juicio más necesitaba un poeta:

:::space{lines=0.5}

> I had not a dispute, but a disquisition, with Dilke upon various subjects; several things dove-tailed in my mind, and at once it struck me what quality went to form a Man of Achievement, especially in Literature, and which Shakspeare possessed so enormously—I mean Negative Capability, that is, when a man is capable of being in uncertainties, mysteries, doubts, without any irritable reaching after fact and reason. [@keats-letters, 48]

:::space{lines=0.5}

Leído junto a la carta, el dístico final es menos una doctrina que una renuncia. La urna no ha respondido a ninguna de las preguntas del hablante sobre quiénes son las figuras o dónde está el pueblo. Lo que ofrece a cambio es una frase que no se puede comprobar, y el comentario del poeta acepta que eso es todo lo que la urna va a dar. El hablante que empezó exigiendo una historia termina conforme con saber a medias, que es el estado que la carta pide al poeta que soporte.

## Obras citadas {style="works-cited"}

:::bibliography{title=""}

:::paragraphs{style="colophon"}
Compuesto en Spectral y Spectral SC (SIL OFL). Texto: ensayo original, CC BY 4.0; citas del libro de Keats de 1820 y de la edición de sus cartas de Colvin.
:::

:::references{format=csl-yaml}
- id: keats-ode
  type: chapter
  author: [{family: Keats, given: John}]
  title: Ode on a Grecian Urn
  title-short: Ode
  container-title: "Lamia, Isabella, The Eve of St. Agnes, and Other Poems"
  publisher: Taylor and Hessey
  issued: 1820
  page: 113-116
- id: keats-letters
  type: book
  author: [{family: Keats, given: John}]
  editor: [{family: Colvin, given: Sidney}]
  title: Letters of John Keats to His Family and Friends
  title-short: Letters
  publisher: Macmillan
  issued: 1925
- id: jack1967
  type: book
  author: [{family: Jack, given: Ian}]
  title: Keats and the Mirror of Art
  publisher: Clarendon Press
  issued: 1967
- id: mcdermott1948
  type: article-journal
  author: [{family: McDermott, given: William C.}]
  title: Keats and Sosibios
  container-title: The Classical Journal
  volume: 44
  issue: 1
  page: 33-34
  issued: 1948
- id: stillinger1974
  type: book
  author: [{family: Stillinger, given: Jack}]
  title: "The Texts of Keats’s Poems"
  publisher: Harvard UP
  issued: 1974
- id: eliot1932
  type: chapter
  author: [{family: Eliot, given: T. S.}]
  title: Dante
  container-title: "Selected Essays, 1917–1932"
  publisher: Faber and Faber
  issued: 1932
- id: brooks1947
  type: book
  author: [{family: Brooks, given: Cleanth}]
  title: "The Well Wrought Urn: Studies in the Structure of Poetry"
  publisher: Reynal and Hitchcock
  issued: 1947
:::
`;

// ─── 3 · Fonts ──────────────────────────────────────────────────────────────
// Every face the design uses (gotcha: fonts-first).
const FONTS = {
  Spectral: ['300', '300i', '400', '400i', '600', '600i'],
  'Spectral SC': ['400', '500', '600'],
};

// ─── 4 · Build & show ───────────────────────────────────────────────────────
await loadFonts(FONTS, markdown);
const doc = await buildWithFonts(() => buildDocument({ markdown }, config()), markdown);
const title = t({ en: 'A humanities essay with MLA works cited',
  es: 'Un ensayo de humanidades con obras citadas en MLA' });
showPages(doc, { title });
offerPdf(() => renderToPdf(doc, { fontProvider: fontsourceProvider }), `${RECIPE}.pdf`);

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

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

// ─── Kit · fonts 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 · pdf v1 ── the same in every recipe that exports a PDF ──────────────
/** postext-pdf embeds TrueType bytes. Fetch the Fontsource file the screen
 *  used, snapping to a weight the family ships and falling back to upright
 *  when it has no italic: the PDF asks for every face a block could use. */
async function fontsourceProvider(family, weight, style) {
  const id = fontsourceId(family);
  const meta = await fontsourceMeta(family);
  const weights = meta?.weights?.length ? meta.weights : [400, 700];
  const w = weights.reduce((a, b) => (Math.abs(b - weight) < Math.abs(a - weight) ? b : a));
  const s = style === 'italic' && meta && !meta.styles.includes('italic') ? 'normal' : style;
  const res = await fetch(`https://cdn.jsdelivr.net/npm/@fontsource/${id}@5/files/${id}-latin-${w}-${s}.woff2`);
  if (!res.ok) throw new Error(`Fontsource has no ${family} ${w} ${s} (${res.status})`);
  return decompressWoff2(new Uint8Array(await res.arrayBuffer()));
}

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

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

## Variants

### Posa la llista a continuació de l'assaig

Una revista que compon les obres citades després de l'últim paràgraf, sense pàgina nova, treu el salt de l'estil del títol.

```diff
-  headingStyles: [{ id: 'works-cited', breakBefore: { enabled: true, parity: 'any' } }],
+  headingStyles: [{ id: 'works-cited', breakBefore: { enabled: false } }],
```

### Cita en Chicago autor-any

El mateix assaig en Chicago posa l'any després de l'autor, «(Brooks 1947, 165)», i la llista s'hi adapta.

```diff
-  style: 'modern-language-association', // MLA 9th edition, author-page
+  style: 'chicago-author-date',
```

## Errors freqüents

- **Qualsevol objecte headings desactiva el salt de pàgina de l'H1.** Per defecte un H1 salta a una pàgina senar (always-odd), però qualsevol objecte headings anul·la aquest valor, de manera que els capítols van seguits i span: 'page' no fa res. Torna a declarar headings.levels[0].breakBefore: { enabled: true, parity } a cada configuració.
- **Carrega totes les fonts abans de compondre.** La composició mesura el text amb les fonts que el navegador ha carregat i en desa les amplades, així que una font que arriba després de la primera composició deixa talls de línia erronis i un PDF que ja no coincideix amb la pantalla. Carrega abans tots els pesos i estils, i crida clearMeasurementCache() abans de recompondre si alguna arriba tard.
- **Una paleta canviada no arriba als elements de disseny ni al color de les remissions.** postext 1.4.1 aplica colorPalette als estils de text (cos, títols, llistes, peus, taules, requadres), però no als elements de capçaleres, peus de pàgina, obertures i portadelles, ni a bodyText.referenceColor: conserven l'hex escrit al costat del seu paletteId. Si canvies la paleta, per a una edició de pantalla fosca o per recolorejar, reescriu cada color enllaçat a partir de colorPalette abans de compondre.
- **Només 8 llengües tenen partició de mots, amb el codi exacte.** La partició de mots existeix per a en-us, es, fr, de, it, pt, ca i nl, amb el codi exacte: 'es-ES' o qualsevol altra llengua passa sense avís a l'anglès americà.
- **Una configuració es desa a la memòria cau per identitat: crea un objecte nou.** El motor desa a la memòria cau les configuracions resoltes segons la identitat de l'objecte, de manera que modificar el mateix objecte i tornar a compondre reutilitza el resultat anterior. Crea un objecte nou a cada composició: per això la configuració d'una recepta és una funció, config().

- Una cita narrativa sense pàgina, `@stillinger1974 registra`, imprimeix el nom amb un segon espai darrere en postext 1.12.1. Cita aquesta obra entre claudàtors al final de la frase, com fa aquest assaig, o dona'n una pàgina.
- citeproc-js imprimeix els títols tal com vénen a les dades. MLA escriu els títols anglesos amb majúscula a cada paraula principal, així que escriu-los així a les referències.

## Crèdits

- Recepta: Ignacio Ferro ([@drnachio](https://github.com/drnachio))
- Text: Quotations from “Ode on a Grecian Urn” (1820): John Keats ([font](https://rpo.library.utoronto.ca/content/ode-grecian-urn)), domini públic
- Text: Quotations from two letters of 1817, in Letters of John Keats to His Family and Friends, ed. Sidney Colvin (1925): John Keats ([font](https://www.gutenberg.org/ebooks/35698)), domini públic
- Tipus de lletra: Spectral (OFL-1.1), Spectral SC (OFL-1.1)
- Codi: MIT · Contingut d'exemple: CC-BY-4.0

## Relacionades

- [Núm. 089 · Un assaig d'història amb notes de Chicago](https://postext.dev/ca/cookbook/history-essay-chicago-notes.md): Un assaig d'història de quatre pàgines on les citacions [@clau, 87] surten com a notes de Chicago a peu de pàgina: completes la primera vegada, breus després. · Nivell 2 (Intermedi) · Articles i treballs acadèmics
- [Núm. 087 · Un capítol de tesi citat en APA 7](https://postext.dev/ca/cookbook/apa-thesis-with-bibtex.md): Un capítol de tesi doctoral amb citacions [@clau, p. 33] que surten en APA 7 gràcies a citeproc-js, amb la llista de referències presa d'un bloc BibTeX. · Nivell 2 (Intermedi) · Articles i treballs acadèmics, Informes i memòries
- [Núm. 098 · Una obra col·lectiva amb bibliografia a cada capítol](https://postext.dev/ca/cookbook/edited-volume-chapter-bibliographies.md): Tres assajos de tres autors, cadascun un document propi a buildBundle, i cadascun tancat per una llista Chicago autor-data amb només les obres que cita. · Nivell 3 (Avançat) · Articles i treballs acadèmics, Llibres de text
