# Un ensayo de humanidades con obras citadas en MLA

> Un ensayo literario cuyas citas [@clave, 48] salen en MLA 9, autor y página, con una cita en bloque y la lista de obras citadas tomada de datos CSL-YAML.

- Versión HTML: https://postext.dev/es/cookbook/mla-humanities-essay
- Receta N.º 096 · Estructura del libro · Nivel 2 (Intermedio) · Salidas: Canvas, PDF
- Géneros: Artículos y trabajos académicos
- Requiere postext ≥ 1.12.1, postext-pdf ≥ 1.12.1 · probada con 1.12.1, postext-pdf 1.12.1 el 2026-10-01
- Páginas: [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
- Abrir en el Sandbox: https://postext.dev/es/sandbox#recipe=mla-humanities-essay&lang=es (.postext: https://postext.dev/cookbook/mla-humanities-essay/es/mla-humanities-essay.postext)
- Última actualización: 2026-10-01
- Otros idiomas: [en](https://postext.dev/en/cookbook/mla-humanities-essay.md), [zh](https://postext.dev/zh/cookbook/mla-humanities-essay.md)

## En pocas palabras

Un ensayo sobre un poema de Keats, tal como lo imprime una revista literaria. La autora escribe una clave corta para cada fuente; Postext imprime el autor y la página entre paréntesis y la lista de obras citadas al final, en estilo MLA.

## Lo que vas a componer

Un ensayo de una revista literaria trimestral: cuatro páginas sobre quién dice los dos últimos versos de la «Ode on a Grecian Urn» de Keats. La página mide 150 × 230 mm y va en Spectral, con los títulos de sección y las cabeceras en Spectral SC; la apertura ocupa una banda del negro del barniz sobre una franja de arcilla roja, los dos colores de un vaso ático. Las citas siguen MLA 9, el estilo de casi todos los departamentos de literatura e historia del arte: autor y página entre paréntesis, solo la página cuando la frase ya nombra al autor, un título abreviado cuando dos obras son del mismo autor y la lista de obras citadas en página aparte. Cada cita es una clave en el texto y el estilo es un solo ajuste.

**Esta receta responde a:**

- ¿Cómo cito en MLA y monto la lista de obras citadas de un ensayo de literatura?

## La respuesta corta

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

## Ingredientes

**Enseña**

- [Citas en un estilo de cita](https://postext.dev/es/docs/document-format.md#citas-y-bibliografía): Obras citadas como [@clave, pág. 33] y formateadas en un estilo CSL (APA, Chicago, MLA, IEEE, Vancouver, ISO 690, GB/T 7714…) elegido en los ajustes, enlazadas con sus entradas.
- [Bibliografía a partir de las referencias](https://postext.dev/es/docs/document-format.md#citas-y-bibliografía): La lista de obras citadas, construida a partir de las referencias del documento (front matter o un bloque BibTeX) donde está :::bibliography o tras el último capítulo.

**También usa**

- [Aperturas diseñadas](https://postext.dev/es/docs/configuration.md#span-y-diseño-avanzado)
- [Atributos de título](https://postext.dev/es/docs/document-format.md#atributos-de-encabezado)
- [Estilos de título](https://postext.dev/es/docs/configuration.md#estilos-de-encabezado)
- [Cabeceras y folios](https://postext.dev/es/docs/configuration.md#encabezados-y-pies)
- [Márgenes simétricos](https://postext.dev/es/docs/configuration.md#márgenes-simétricos-espejo)
- [Estilos de párrafo](https://postext.dev/es/docs/configuration.md#estilos-de-párrafo)
- [Paleta de color semántica](https://postext.dev/es/docs/configuration.md#paleta-de-colores)
- [Exportación a PDF](https://postext.dev/es/docs/configuration.md#generación-de-pdf)
- [Referencias cruzadas](https://postext.dev/es/docs/document-format.md#referencias-cruzadas-y-anclas)
- [Color del papel](https://postext.dev/es/docs/configuration.md#página)
- [Cabeceras según el tipo de página](https://postext.dev/es/docs/configuration.md#elementos-de-texto)
- [Fuentes incrustadas en el PDF](https://postext.dev/es/docs/configuration.md#por-qué-un-proveedor-de-fuentes)
- [Espacio vertical explícito](https://postext.dev/es/docs/document-format.md#space)

**La configuración de un vistazo**

- [`bodyText`](https://postext.dev/es/docs/configuration.md#texto-de-cuerpo), [`citations`](https://postext.dev/es/docs/configuration.md#citas), [`colorPalette`](https://postext.dev/es/docs/configuration.md#paleta-de-colores), [`footer`](https://postext.dev/es/docs/configuration.md#encabezados-y-pies), [`header`](https://postext.dev/es/docs/configuration.md#encabezados-y-pies), [`headingStyles`](https://postext.dev/es/docs/configuration.md#estilos-de-encabezado), [`headings`](https://postext.dev/es/docs/configuration.md#encabezados), [`layout`](https://postext.dev/es/docs/configuration.md#disposición), [`locale`](https://postext.dev/es/docs/configuration.md#separación-silábica), [`page`](https://postext.dev/es/docs/configuration.md#página), [`paragraphStyles`](https://postext.dev/es/docs/configuration.md#estilos-de-párrafo)

**API**

- [`LOCALES`](https://postext.dev/es/docs/document-format.md#citas-y-bibliografía), [`STYLES`](https://postext.dev/es/docs/document-format.md#citas-y-bibliografía), [`buildDocument`](https://postext.dev/es/docs/configuration.md#construir-un-documento), [`clearMeasurementCache`](https://postext.dev/es/docs/configuration.md#caché-de-medidas), [`createCiteprocEngine`](https://postext.dev/es/docs/document-format.md#citas-y-bibliografía), [`decompressWoff2`](https://postext.dev/es/docs/configuration.md#proveedor-de-fuentes-en-el-navegador-fontsource--woff2), [`registerCitationEngine`](https://postext.dev/es/docs/document-format.md#citas-y-bibliografía), [`renderPageToCanvas`](https://postext.dev/es/docs/configuration.md#renderizar-una-página-a-un-bitmap), [`renderToPdf`](https://postext.dev/es/docs/configuration.md#generación-de-pdf)

**Tipografías**

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

## Elaboración

### 1 · El estilo es MLA; los datos, CSL

El código es [la respuesta corta](#la-respuesta-corta) de arriba. El motor de citas se registra una vez y `citations.style` nombra el estilo MLA incluido; citeproc-js redacta con él cada cita y la lista. Las referencias van en un bloque `:::references{format=csl-yaml}` al final del ensayo. Aquí se usa CSL-YAML y no BibTeX porque MLA necesita un título abreviado para las dos obras de Keats, y el lector de BibTeX de postext 1.12.1 descarta el campo `shorttitle`; en CSL el campo se llama `title-short`.

### 2 · Tres maneras de citar

El texto cita una obra con `[@jack1967]`, que imprime «(Jack)» porque el ensayo se refiere al libro entero. Con página, `[@keats-letters, 41]` imprime «(Keats, *Letters* 41)»: se citan dos obras de Keats, así que citeproc añade el título abreviado para distinguirlas. Cuando la frase ya nombra al crítico, `[-@eliot1932, 230]` omite el nombre e imprime «(230)». Una vez citada la oda con sus números de verso, las citas siguientes llevan solo los números, escritos entre paréntesis normales, como permite MLA.

### 3 · Una cita en bloque con la referencia tras el punto

```js
// script.js, líneas 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 pasa de cuatro líneas de prosa, así que va aparte como cita en bloque de Markdown: en redonda, en el color del texto, sangrada 2 em y sin sangría de primera línea. MLA pone la referencia de una cita en bloque detrás del punto final, y el original hace lo mismo: `…fact and reason. [@keats-letters, 48]`. Un `:::space{lines=0.5}` encima y otro debajo la separan de los párrafos que la rodean.

### 4 · Las obras citadas, en página aparte

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

El título lleva un estilo que salta a página nueva, y `:::bibliography{title=""}` coloca la lista debajo sin un segundo título. La lista va en orden alfabético con sangría francesa, y la segunda obra de Keats empieza con una raya triple en lugar del nombre, como pide MLA para los autores repetidos. Un folio al pie aparece en esta página y en la apertura, donde no hay cabeceras.

### 5 · La apertura en los dos colores de un vaso

```js
// script.js, líneas 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íneas 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 mide 116 mm y lleva el antetítulo, el título de 56 pt en Spectral Light cursiva, el subtítulo y la firma, todos tomados de atributos del título. La franja de arcilla es una segunda caja anclada 3 mm por encima del pie de la banda.

## La receta completa

Un solo archivo, compuesto a partir de la carpeta de la receta con el texto de ejemplo y el kit común del Recetario ya incluidos; construye su propia página. Para ejecutarlo, ponlo en un `<script type="module">` de una página vacía o pégalo en el panel JS de un pen nuevo de CodePen (como módulo). Importa postext desde esm.sh, así que no hay nada que instalar ni compilar.

- Carpeta de la receta: 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 ───────────────────────────────────────────────────────────────────────
```

## Variantes

### Pon la lista a continuación del ensayo

Una revista que compone las obras citadas tras el último párrafo, sin página nueva, quita el salto del estilo del título.

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

### Cita en Chicago autor-año

El mismo ensayo en Chicago pone el año tras el autor, «(Brooks 1947, 165)», y la lista se adapta.

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

## Errores frecuentes

- **Cualquier objeto headings desactiva el salto de página del H1.** Por defecto un H1 salta a una página impar (always-odd), pero cualquier objeto headings anula ese valor, así que los capítulos van seguidos y span: 'page' no hace nada. Vuelve a declarar headings.levels[0].breakBefore: { enabled: true, parity } en cada configuración.
- **Carga todas las fuentes antes de componer.** La composición mide el texto con las fuentes que el navegador ha cargado y guarda los anchos, así que una fuente que llega después de la primera composición deja cortes de línea erróneos y un PDF que ya no coincide con la pantalla. Carga antes todos los pesos y estilos, y llama a clearMeasurementCache() antes de recomponer si alguna llega tarde.
- **Una paleta cambiada no llega a los elementos de diseño ni al color de las remisiones.** postext 1.4.1 aplica colorPalette a los estilos de texto (cuerpo, títulos, listas, pies, tablas, recuadros), pero no a los elementos de cabeceras, pies de página, aperturas y portadillas, ni a bodyText.referenceColor: conservan el hex escrito junto a su paletteId. Si cambias la paleta, para una edición de pantalla oscura o para recolorear, reescribe cada color enlazado a partir de colorPalette antes de componer.
- **Solo 8 idiomas tienen separación silábica, con el código exacto.** La separación silábica existe para en-us, es, fr, de, it, pt, ca y nl, con el código exacto: 'es-ES' o cualquier otro idioma pasa sin aviso al inglés americano.
- **Una configuración se cachea por identidad: crea un objeto nuevo.** El motor guarda en caché las configuraciones resueltas según la identidad del objeto, así que modificar el mismo objeto y volver a componer reutiliza el resultado anterior. Crea un objeto nuevo en cada composición: por eso la configuración de una receta es una función, config().

- Una cita narrativa sin página, `@stillinger1974 registra`, imprime el nombre con un segundo espacio detrás en postext 1.12.1. Cita esa obra entre corchetes al final de la frase, como hace este ensayo, o da una página.
- citeproc-js imprime los títulos tal como vienen en los datos. MLA escribe los títulos ingleses con mayúscula en cada palabra principal, así que escríbelos así en las referencias.

## Créditos

- Receta: Ignacio Ferro ([@drnachio](https://github.com/drnachio))
- Texto: Citas de «Ode on a Grecian Urn» (1820): John Keats ([fuente](https://rpo.library.utoronto.ca/content/ode-grecian-urn)), dominio público
- Texto: Citas de dos cartas de 1817, en Letters of John Keats to His Family and Friends, ed. de Sidney Colvin (1925): John Keats ([fuente](https://www.gutenberg.org/ebooks/35698)), dominio público
- Tipografías: Spectral (OFL-1.1), Spectral SC (OFL-1.1)
- Código: MIT · Contenido de ejemplo: CC-BY-4.0

## Relacionadas

- [N.º 089 · Un ensayo de historia con notas de Chicago](https://postext.dev/es/cookbook/history-essay-chicago-notes.md): Un ensayo de historia de cuatro páginas cuyas citas [@clave, 87] salen como notas de Chicago al pie: completas la primera vez, breves después. · Nivel 2 (Intermedio) · Artículos y trabajos académicos
- [N.º 087 · Un capítulo de tesis citado en APA 7](https://postext.dev/es/cookbook/apa-thesis-with-bibtex.md): Un capítulo de tesis doctoral cuyas citas [@clave, p. 33] salen en APA 7 gracias a citeproc-js, con la lista de referencias tomada de un bloque BibTeX. · Nivel 2 (Intermedio) · Artículos y trabajos académicos, Informes y memorias
- [N.º 098 · Una obra colectiva con bibliografía en cada capítulo](https://postext.dev/es/cookbook/edited-volume-chapter-bibliographies.md): Tres ensayos de tres autores, cada uno un documento propio en buildBundle, y cada uno cerrado por una lista Chicago autor-fecha con solo las obras que cita. · Nivel 3 (Avanzado) · Artículos y trabajos académicos, Libros de texto
