# Un cancionero con melodía, letra y acordes

> Cada canción es un bloque ABC: melodía, cifrados sobre el pentagrama y primera estrofa bajo las notas, grabados al ancho de columna con un estilo de partitura.

- Versión HTML: https://postext.dev/es/cookbook/songbook-melody-words-chords
- Receta N.º 164 · Publicaciones completas · Nivel 2 (Intermedio) · Salidas: Canvas, PDF
- Géneros: Poesía, Hojas sueltas y efímeros
- Requiere postext ≥ 1.54.0, postext-pdf ≥ 1.54.0 · probada con 1.54.0, postext-pdf 1.54.0 el 2026-10-22
- Páginas: [1](https://postext.dev/cookbook/songbook-melody-words-chords/es/p01.webp?v=e41f37fa), [2](https://postext.dev/cookbook/songbook-melody-words-chords/es/p02.webp?v=e41f37fa), [3](https://postext.dev/cookbook/songbook-melody-words-chords/es/p03.webp?v=e41f37fa), [4](https://postext.dev/cookbook/songbook-melody-words-chords/es/p04.webp?v=e41f37fa), [5](https://postext.dev/cookbook/songbook-melody-words-chords/es/p05.webp?v=e41f37fa)
- PDF: https://postext.dev/cookbook/songbook-melody-words-chords/es/songbook-melody-words-chords.pdf?v=e41f37fa
- Abrir en el Sandbox: https://postext.dev/es/sandbox#recipe=songbook-melody-words-chords&lang=es (.postext: https://postext.dev/cookbook/songbook-melody-words-chords/es/songbook-melody-words-chords.postext)
- Última actualización: 2026-10-22
- Otros idiomas: [en](https://postext.dev/en/cookbook/songbook-melody-words-chords.md), [ca](https://postext.dev/ca/cookbook/songbook-melody-words-chords.md), [de](https://postext.dev/de/cookbook/songbook-melody-words-chords.md), [fr](https://postext.dev/fr/cookbook/songbook-melody-words-chords.md), [it](https://postext.dev/it/cookbook/songbook-melody-words-chords.md), [pt](https://postext.dev/pt/cookbook/songbook-melody-words-chords.md), [zh](https://postext.dev/zh/cookbook/songbook-melody-words-chords.md), [ja](https://postext.dev/ja/cookbook/songbook-melody-words-chords.md), [ar](https://postext.dev/ar/cookbook/songbook-melody-words-chords.md)

## En pocas palabras

Un cancionero pequeño para una merienda. Cada canción se escribe como texto en una notación musical llamada ABC, y Postext dibuja el pentagrama, las notas, los acordes de guitarra y la letra bajo las notas.

## Lo que vas a componer

El *Cancionero de la merienda*, un cuadernillo en A5 para la tarde de un cumpleaños: una portada en rojo granza, una nota para quien canta con el índice, tres canciones y, al final, de dónde viene cada una. Cada canción se abre bajo un número grande en granza, el título en Fraunces y una línea de créditos en versalitas. Debajo, la melodía en un pentagrama de 5,5 mm con la fuente musical Leland, los acordes de guitarra sobre los compases y la primera estrofa bajo las notas en Spectral, la letra del libro. Las coplas restantes van a dos columnas. Como las tres canciones son cortas, dos de ellas comparten página. La edición inglesa es un cancionero de sobremesa con tres canciones largas, una por página.

**Esta receta responde a:**

- ¿Cómo compongo un cancionero con la melodía, la letra bajo las notas y los cifrados de acordes?

## La respuesta corta

````js
// script.js, líneas 33–50
// A song is an ```abc fence in the Markdown: the melody, its chords written "G" before the note
// they fall on, and the first verse on a w: line under it. The look is set here, once.
const scoreStyle = {
  staffSize: mm(5.5), // a songbook rastral; the words under it come out at about 8 pt
  musicFont: 'Leland', // lighter noteheads than Bravura, close to Spectral's colour
  textFont: TEXT, // words and chord names in the text face, not the engraver's Times
  spacing: 1, // the engraver's own: five or six bars of 3/4 to a system on this measure
  barNumbers: 'none', // a table of singers counts verses, not bars
  systemGap: mm(4.5), // room for the chord names between one system's words and the next
  marginTop: pt(4),
  marginBottom: pt(LEAD),
  color: col('ink'),
  minSystemsAtBreak: 2, // never a lone system at a page foot or head
};
// Two habits of Verovio's ABC reader shape the w: lines. Every sung note takes the next
// syllable in order: the `_` and `*` holds are read as nothing, so the second note of a
// slurred pair (B/G/ under "-ing") gets a no-break space, which prints nothing. A tied note
// takes none. `~` joins two syllables on one note: "de~a-trás".
````

## Ingredientes

**Enseña**

- [Notación musical](https://postext.dev/es/docs/music.md#una-partitura-en-el-texto): Partituras escritas como texto en bloques ```abc, ```musicxml, ```mei o ```humdrum, grabadas por Verovio al ancho de la columna y cortadas en sistemas que siguen en la columna o la página siguiente.
- [Estilo de partitura](https://postext.dev/es/docs/configuration-resources.md#estilo-de-partitura): scoreStyle y los estilos con nombre de scoreStyles: tamaño del pentagrama, fuente musical, fuente del texto, espaciado, números de compás, separación entre sistemas, márgenes, tinta y cuántos sistemas se mantienen juntos en un corte.

**También usa**

- [Verso línea a línea](https://postext.dev/es/docs/document-format-directives.md#verse)
- [Aperturas diseñadas](https://postext.dev/es/docs/configuration-headings.md#apertura-a-toda-página-y-diseño-avanzado)
- [Atributos de título](https://postext.dev/es/docs/document-format.md#atributos-de-encabezado)
- [Títulos numerados](https://postext.dev/es/docs/configuration-headings.md#configuración-por-nivel)
- [Estilos de título](https://postext.dev/es/docs/configuration-styles.md#estilos-de-encabezado)
- [Capítulos sin número](https://postext.dev/es/docs/configuration-styles.md#estilos-de-encabezado)
- [Cubiertas, portadas y colofones](https://postext.dev/es/docs/configuration-styles.md#estilos-de-encabezado)
- [Metadatos del documento](https://postext.dev/es/docs/document-format.md#frontmatter)
- [Índice general](https://postext.dev/es/docs/configuration-notes-references.md#índice-general)
- [Cabeceras y folios](https://postext.dev/es/docs/configuration-headers-footers.md#cabeceras-y-pies)
- [Cabeceras según el tipo de página](https://postext.dev/es/docs/configuration-headers-footers.md#elementos-de-texto)
- [Estilos de párrafo](https://postext.dev/es/docs/configuration-styles.md#estilos-de-párrafo)
- [Márgenes simétricos](https://postext.dev/es/docs/configuration-page-layout.md#márgenes-simétricos-espejo)
- [Paleta de color semántica](https://postext.dev/es/docs/configuration-fonts-colors-viewers.md#paleta-de-colores)
- [Color del papel](https://postext.dev/es/docs/configuration-page-layout.md#página)
- [Textos, filetes y cajas en los diseños de página](https://postext.dev/es/docs/configuration-headers-footers.md#cabeceras-y-pies)
- [Exportación a PDF](https://postext.dev/es/docs/configuration-pdf-output.md#generación-de-pdf)
- [Columnas en un recuadro o en el texto](https://postext.dev/es/docs/document-format-directives.md#columns)
- [Listados de código](https://postext.dev/es/docs/configuration-styles.md#listados-de-código)
- [Fuentes incrustadas en el PDF](https://postext.dev/es/docs/configuration-pdf-output.md#por-qué-un-proveedor-de-fuentes)
- [Cabeceras por sección](https://postext.dev/es/docs/configuration-styles.md#estilos-de-encabezado)
- [Superíndices y subíndices](https://postext.dev/es/docs/document-format-inline.md#formato-en-línea)

**La configuración de un vistazo**

- [`bodyText`](https://postext.dev/es/docs/configuration-text.md#texto-base), [`colorPalette`](https://postext.dev/es/docs/configuration-fonts-colors-viewers.md#paleta-de-colores), [`footer`](https://postext.dev/es/docs/configuration-headers-footers.md#cabeceras-y-pies), [`header`](https://postext.dev/es/docs/configuration-headers-footers.md#cabeceras-y-pies), [`headingStyles`](https://postext.dev/es/docs/configuration-styles.md#estilos-de-encabezado), [`headings`](https://postext.dev/es/docs/configuration-headings.md#encabezados), [`layout`](https://postext.dev/es/docs/configuration-page-layout.md#disposición), [`locale`](https://postext.dev/es/docs/configuration-language.md#separación-silábica), [`page`](https://postext.dev/es/docs/configuration-page-layout.md#página), [`paragraphStyles`](https://postext.dev/es/docs/configuration-styles.md#estilos-de-párrafo), [`scoreStyle`](https://postext.dev/es/docs/configuration-resources.md#estilo-de-partitura), [`toc`](https://postext.dev/es/docs/configuration-notes-references.md#índice-general)

**API**

- [`buildDocumentWithFonts`](https://postext.dev/es/docs/configuration-programmatic-usage.md#cargar-las-fuentes-antes-de-componer), [`decompressWoff2`](https://postext.dev/es/docs/configuration-pdf-output.md#proveedor-de-fuentes-en-el-navegador-fontsource--woff2), [`initMusicEngine`](https://postext.dev/es/docs/music.md#una-partitura-en-el-texto), [`renderPageToCanvas`](https://postext.dev/es/docs/configuration-programmatic-usage.md#renderizar-una-página-como-mapa-de-bits), [`renderToPdf`](https://postext.dev/es/docs/configuration-pdf-output.md#generación-de-pdf)

**Tipografías**

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

## Elaboración

### 1 · Escribe la canción y define la partitura una sola vez

El estilo de partitura es [la respuesta corta](https://postext.dev/es/cookbook/songbook-melody-words-chords.md#la-respuesta-corta) de arriba. En el Markdown cada canción es un bloque ```` ```abc ````: las líneas de cabecera dan el compás, la figura de referencia y la tonalidad; un acorde entre comillas (`"C7"B`) se imprime sobre la nota que lo sigue, y una línea `w:` bajo la música pone una sílaba bajo cada nota, separadas con guiones. Una canción con estribillo y copla son dos líneas de música, cada una con su `w:`. El tamaño del pentagrama, la fuente musical, la letra de las palabras, los números de compás y el espacio entre sistemas salen de `scoreStyle`: las tres canciones casan y los bloques solo llevan música.

### 2 · Abre cada canción con su número y sus créditos

```js
// script.js, líneas 66–81
// # Amazing Grace {credit="Words John Newton, 1779 · Tune New Britain, American, 1835"}
// The songs run on, three lines apart: a short song shares its page with the next one.
const at = (to, edge, x = 0, y = 0) => ({ anchor: { to, edge }, offset: { x: mm(x), y: mm(y) } });
const text = (id, content, font, size, color, placement, extra = {}) => ({ kind: 'text', id,
  content, fontFamily: font, fontSize: pt(size), color: col(color), align: 'left', placement,
  ...extra });
const songHead = { enabled: true, minHeight: mm(21), slot: { elements: [
  text('number', '{number}', DISPLAY, 26, 'madder', at('container', 'top-left', 0, 1),
    { fontWeight: 600, lineHeight: 1 }),
  text('title', '{titleText}', DISPLAY, 21, 'ink', { ...at('container', 'top-left', 12, 2),
    size: { width: mm(100) } }, { fontWeight: 600, lineHeight: 1.05 }),
  text('credit', '{attr.credit}', LABEL, 8.4, 'madder', { ...at('#title', 'below', 0, 2.2),
    size: { width: mm(100) } }, { fontWeight: 600, letterSpacing: pt(0.6) }),
] } };
const songs = { level: 1, numberingTemplate: '{1}', advancedDesign: songHead,
  marginTop: pt(LEAD * 3), marginBottom: pt(0), breakBefore: { enabled: false } };
```

La canción es un título de primer nivel con un atributo `credit`, y su diseño compone con él el número, el título y la línea de créditos. `minHeight` da a todas la misma cabeza de 21 mm, así que los pentagramas empiezan a la misma altura. Las canciones van seguidas, con tres líneas de aire sobre cada título, y una partitura nunca se corta antes de su segundo sistema (`minSystemsAtBreak: 2`): *Cumpleaños feliz* y *Martinillo* caben en la misma página, y *La cucaracha* pasa entera a la siguiente.

### 3 · Pon las demás coplas a dos columnas

```js
// script.js, líneas 54–62
// :::columns{count=2} holds one :::verse block per stanza; each line of the stanza is a
// line of Markdown, the turned lines indented two spaces (one em). A hymn in long lines
// (Amazing Grace) drops the columns: its stanzas stand one under another, centred.
const verse = { id: 'verse', fontSize: pt(9.4), lineHeight: pt(13), hyphenation: false,
  marginBottom: pt(13 / 2) };
const chorus = { id: 'chorus', fontSize: pt(9.4), lineHeight: pt(13), italic: true,
  hyphenation: false, marginBottom: pt(LEAD / 2) };
const colophon = { id: 'colophon', fontFamily: LABEL, fontSize: pt(7.8), lineHeight: pt(11),
  color: col('muted'), textAlign: 'left', firstLineIndent: pt(0), marginTop: pt(LEAD * 2) };
```

Cada copla es un bloque `:::verse`, así que sus versos cortan donde los cortó la tradición y el segundo y el cuarto entran un cuadratín. Un grupo `:::columns{count=2 gap=4mm}` las pone una al lado de otra a 9,4 pt. Cuenta antes los caracteres: los octosílabos de *La cucaracha* caben de sobra en una columna de 55 mm; los versos largos de *Amazing Grace*, en la edición inglesa, no, y por eso esa canción deja las estrofas en un solo bloque centrado.

### 4 · Deja tranquilos los elementos fijos

```js
// script.js, líneas 119–131
const head = (id, content, parity, edge, x, style = {}) => ({
  kind: 'text', id, content, parity, pages: 'all', // a song's first page carries them too
  fontFamily: LABEL, fontSize: pt(8.4), fontWeight: 600, letterSpacing: pt(0.9),
  color: col('muted'), ...style,
  placement: { anchor: { to: 'page', edge }, offset: { x: mm(x), y: mm(12.5) } },
});
const folio = { fontFamily: TEXT, fontWeight: 600, letterSpacing: pt(0), color: col('madder') };
const header = { elements: [
  head('verso-folio', '{pageNumber}', 'even', 'top-left', OUTER, folio),
  head('verso-title', '{title}', 'even', 'top-left', OUTER + 8),
  head('recto-song', '{chapterTitle}', 'odd', 'top-right', -(OUTER + 8)),
  head('recto-folio', '{pageNumber}', 'odd', 'top-right', -OUTER, folio),
] };
```

Las cabeceras están en todas las páginas de canciones: el título del libro en la página par y la canción en la impar, con el folio en el granza de los números. La portada y la nota para quien canta las quitan con un `header` vacío en sus estilos de título.

## 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/songbook-melody-words-chords

### script.js

````js
// ═══ Postext Cookbook · Nº 164 · Songbook: melody, words under the notes, chords ═══════
// https://postext.dev/en/cookbook/songbook-melody-words-chords
// Code: MIT · Words and tunes: public domain (see the notes in each edition) · Prose: CC BY 4.0
// Fonts: Spectral, Spectral SC, Fraunces (SIL OFL 1.1); Leland (music) · Needs postext ≥ 1.54.0
import {
  buildDocumentWithFonts, renderPageToCanvas, initMusicEngine,
} from 'https://esm.sh/postext';
import { renderToPdf, decompressWoff2 } from 'https://esm.sh/postext-pdf';

const LANG = 'es'; // @lang: the language of the sample document ('en' | 'es')
const RECIPE = 'songbook-melody-words-chords';

// ─── 1 · Design ─────────────────────────────────────────────────────────────
const palette = { // every colour in the config links to one of these
  ink: '#211e1b', // text and notes: a warm near-black
  madder: '#8a3324', // the one accent: song numbers, the title page, credit lines
  tint: '#f1e7d8', // the subtitle on the madder ground (8.2:1)
  rule: '#cbbca6', // hairlines
  muted: '#675e55', // running heads, notes and the colophon (5.9:1 on the paper)
  paper: '#fbf8f1', // a cream stock
};
const col = (id) => ({ hex: palette[id], model: 'hex', paletteId: id });
const colorPalette = [
  ...Object.entries(palette).map(([id, hex]) => ({ id, name: id, value: { hex, model: 'hex' } })),
  // The engine's defaults link to 'main-color': point it at the accent, so nothing prints blue.
  { id: 'main-color', name: 'madder (defaults)', value: { hex: palette.madder, model: 'hex' } },
];
const [TEXT, LABEL, DISPLAY] = ['Spectral', 'Spectral SC', 'Fraunces'];
const LEAD = 14; // pt: the body's leading
const OUTER = 15; // mm: the outer margin, where the running heads and folios align

// #region answer: the score style: a 5 mm staff in Leland, the words in the book's face
// A song is an ```abc fence in the Markdown: the melody, its chords written "G" before the note
// they fall on, and the first verse on a w: line under it. The look is set here, once.
const scoreStyle = {
  staffSize: mm(5.5), // a songbook rastral; the words under it come out at about 8 pt
  musicFont: 'Leland', // lighter noteheads than Bravura, close to Spectral's colour
  textFont: TEXT, // words and chord names in the text face, not the engraver's Times
  spacing: 1, // the engraver's own: five or six bars of 3/4 to a system on this measure
  barNumbers: 'none', // a table of singers counts verses, not bars
  systemGap: mm(4.5), // room for the chord names between one system's words and the next
  marginTop: pt(4),
  marginBottom: pt(LEAD),
  color: col('ink'),
  minSystemsAtBreak: 2, // never a lone system at a page foot or head
};
// Two habits of Verovio's ABC reader shape the w: lines. Every sung note takes the next
// syllable in order: the `_` and `*` holds are read as nothing, so the second note of a
// slurred pair (B/G/ under "-ing") gets a no-break space, which prints nothing. A tied note
// takes none. `~` joins two syllables on one note: "de~a-trás".
// #endregion

// #region verse: the other verses, two stanzas side by side under the score
// :::columns{count=2} holds one :::verse block per stanza; each line of the stanza is a
// line of Markdown, the turned lines indented two spaces (one em). A hymn in long lines
// (Amazing Grace) drops the columns: its stanzas stand one under another, centred.
const verse = { id: 'verse', fontSize: pt(9.4), lineHeight: pt(13), hyphenation: false,
  marginBottom: pt(13 / 2) };
const chorus = { id: 'chorus', fontSize: pt(9.4), lineHeight: pt(13), italic: true,
  hyphenation: false, marginBottom: pt(LEAD / 2) };
const colophon = { id: 'colophon', fontFamily: LABEL, fontSize: pt(7.8), lineHeight: pt(11),
  color: col('muted'), textAlign: 'left', firstLineIndent: pt(0), marginTop: pt(LEAD * 2) };
// #endregion

// #region song-head: a song opens under its number, its title and its credit line
// # Amazing Grace {credit="Words John Newton, 1779 · Tune New Britain, American, 1835"}
// The songs run on, three lines apart: a short song shares its page with the next one.
const at = (to, edge, x = 0, y = 0) => ({ anchor: { to, edge }, offset: { x: mm(x), y: mm(y) } });
const text = (id, content, font, size, color, placement, extra = {}) => ({ kind: 'text', id,
  content, fontFamily: font, fontSize: pt(size), color: col(color), align: 'left', placement,
  ...extra });
const songHead = { enabled: true, minHeight: mm(21), slot: { elements: [
  text('number', '{number}', DISPLAY, 26, 'madder', at('container', 'top-left', 0, 1),
    { fontWeight: 600, lineHeight: 1 }),
  text('title', '{titleText}', DISPLAY, 21, 'ink', { ...at('container', 'top-left', 12, 2),
    size: { width: mm(100) } }, { fontWeight: 600, lineHeight: 1.05 }),
  text('credit', '{attr.credit}', LABEL, 8.4, 'madder', { ...at('#title', 'below', 0, 2.2),
    size: { width: mm(100) } }, { fontWeight: 600, letterSpacing: pt(0.6) }),
] } };
const songs = { level: 1, numberingTemplate: '{1}', advancedDesign: songHead,
  marginTop: pt(LEAD * 3), marginBottom: pt(0), breakBefore: { enabled: false } };
// #endregion

// #region front: the title page on a madder field, then the note to the singers
const display = (id, content, font, size, color, y, extra = {}) => text(id, content, font, size,
  color, { ...at('page', 'top', 0, y), size: { width: mm(118) } }, { align: 'center', ...extra });
const titlePage = { id: 'title-page', numbered: false, toc: false, span: 'page',
  header: { elements: [] }, footer: { elements: [] },
  advancedDesign: { enabled: true, slot: { elements: [
    { kind: 'box', id: 'field', style: { backgroundColor: col('madder') },
      placement: { ...at('bleed', 'top-left'), size: { width: 'fill', height: mm(132) } } },
    display('kicker', '{author}', LABEL, 10, 'tint', 40, { letterSpacing: pt(2),
      fontWeight: 600 }),
    display('title', '{titleText}', DISPLAY, 46, 'paper', 54, { fontWeight: 600,
      lineHeight: 1 }),
    display('subtitle', '{subtitle}', TEXT, 13, 'tint', 104, { italic: true }),
    display('press', '{attr.press}', LABEL, 9, 'muted', 182, { letterSpacing: pt(1.6),
      fontWeight: 600 }),
  ] } } };
// The note to the singers and the notes at the back are plain headings with no number;
// the front one has no running head and stays out of the contents.
const plain = { numbered: false, advancedDesign: { enabled: false }, fontFamily: DISPLAY,
  fontWeight: 600, fontSize: pt(21), lineHeight: pt(LEAD * 2), marginBottom: pt(LEAD / 2),
  breakBefore: { enabled: true, parity: 'any' } }; // gotcha: style-inherits-break
const front = { ...plain, id: 'front', toc: false, header: { elements: [] } };
const back = { ...plain, id: 'back' };
// #endregion

// #region contents: song number, title, a dotted leader and the page
const toc = {
  levels: [{ level: 1, fontFamily: TEXT, fontSize: pt(10.5), numberFontFamily: DISPLAY,
    numberFontWeight: 600, numberColor: col('madder'), numberWidth: mm(6), numberGap: mm(2) }],
  pageNumber: { fontFamily: LABEL, fontSize: pt(9.5), color: col('muted'), width: mm(6) },
  leader: { char: '. ', gap: mm(2) },
};
// #endregion

// #region heads: the book on the verso, the song on the recto, folios outside
const head = (id, content, parity, edge, x, style = {}) => ({
  kind: 'text', id, content, parity, pages: 'all', // a song's first page carries them too
  fontFamily: LABEL, fontSize: pt(8.4), fontWeight: 600, letterSpacing: pt(0.9),
  color: col('muted'), ...style,
  placement: { anchor: { to: 'page', edge }, offset: { x: mm(x), y: mm(12.5) } },
});
const folio = { fontFamily: TEXT, fontWeight: 600, letterSpacing: pt(0), color: col('madder') };
const header = { elements: [
  head('verso-folio', '{pageNumber}', 'even', 'top-left', OUTER, folio),
  head('verso-title', '{title}', 'even', 'top-left', OUTER + 8),
  head('recto-song', '{chapterTitle}', 'odd', 'top-right', -(OUTER + 8)),
  head('recto-folio', '{pageNumber}', 'odd', 'top-right', -OUTER, folio),
] };
// #endregion

const config = {
  locale: t({ en: 'en-us', es: 'es' }), // each edition's hyphenation patterns
  colorPalette,
  page: { // A5, the size of a parish songbook; mirror: left is the inner margin
    sizePreset: 'custom', width: mm(148), height: mm(210), dpi: 150,
    backgroundColor: col('paper'),
    margins: { top: mm(22), bottom: mm(20), left: mm(19), right: mm(OUTER), mirror: true },
  },
  layout: { layoutType: 'single' },
  bodyText: {
    fontFamily: TEXT, fontSize: pt(10.3), lineHeight: pt(LEAD), color: col('ink'),
    boldColor: col('madder'), 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: DISPLAY, fontWeight: 600, color: col('ink'),
    levels: [songs, { level: 2, fontFamily: LABEL, fontSize: pt(9.5), lineHeight: pt(LEAD),
      letterSpacing: pt(1.4), color: col('madder'), marginTop: pt(LEAD), marginBottom: pt(4) }],
  },
  headingStyles: [titlePage, front, back],
  toc,
  scoreStyle,
  paragraphStyles: [verse, chorus, colophon],
  header,
  footer: { elements: [] },
};

// ─── 2 · Content ────────────────────────────────────────────────────────────
const markdown = `---
title: "Cancionero de la merienda"
subtitle: "Tres canciones para cantar en un cumpleaños"
author: "Edición de Carmen Ibarra"
---

# Cancionero de la merienda {style="title-page" press="Ibarra y Soler · Segunda tirada"}

# A quien canta {style="front"}

Este cuadernillo reúne tres canciones para la tarde de un cumpleaños, cuando la tarta ya está en la mesa y nadie tiene prisa: la felicitación de siempre, un canon que pueden cantar a la vez cuatro grupos de niños y una canción de baile que se sabe medio mundo hispanohablante. Cada una lleva la melodía, los cifrados de guitarra sobre el pentagrama y la primera estrofa bajo las notas; las demás coplas van a continuación, en letra corrida.

Las tonalidades están pensadas para voces sin educar. *Cumpleaños feliz* va en sol mayor y no pasa del re de la cuarta línea; *Martinillo* y *La cucaracha*, en fa mayor. Quien acompañe a la guitarra puede bajar el tono con una cejilla y seguir leyendo los mismos acordes.

Las notas del final cuentan de dónde vienen la letra y la música, y cómo se canta el canon.

## Índice

:::toc

# Cumpleaños feliz {credit="Música de Mildred J. Hill, 1893 · Letra popular"}

\`\`\`abc {alt="Cumpleaños feliz: melodía en sol mayor, compás de tres por cuatro, con cifrados y la letra"}
X:1
T:Cumpleaños feliz
C:Mildred J. Hill
M:3/4
L:1/8
K:G
D>D | "G"E2 D2 G2 | "D7"F4 D>D | E2 D2 A2 | "G"G4 D>D | "G7"d2 B2 G2 | "C"F2 E2 c>c | "G"B2 G2 "D7"A2 | "G"G4 |]
w: Cum-ple-a-ños fe-liz, cum-ple-a-ños fe-liz, te de-se-a-mos to-dos, cum-ple-a-ños fe-liz.
\`\`\`

# Martinillo {credit="Melodía popular francesa, siglo XVIII · Letra popular"}

\`\`\`abc {alt="Martinillo: canon a cuatro voces en fa mayor, compás de cuatro por cuatro, con la letra"}
X:2
T:Martinillo
C:Popular
M:4/4
L:1/4
K:F
"F"F G A F | F G A F | A B c2 | A B c2 | "F"c/d/c/B/ A F | c/d/c/B/ A F | F C F2 | F C F2 |]
w: Mar-ti-ni-llo, Mar-ti-ni-llo, ¿dón-de~es-tás? ¿dón-de~es-tás? To-ca la cam-pa-na, to-ca la cam-pa-na: ¡din, don, dan! ¡din, don, dan!
\`\`\`

# La cucaracha {credit="Canción popular española y mexicana, siglo XIX"}

\`\`\`abc {alt="La cucaracha: melodía en fa mayor, compás de tres por cuatro, estribillo y copla, con cifrados y la letra"}
X:3
T:La cucaracha
C:Popular
M:3/4
L:1/8
K:F
C C C | "F"F2 A C C C | F2 A4 | "C7"F F E E D D | C3 C C C | E2 G C C C | E2 G4 | c d c B A G | "F"F6 |
w: La cu-ca-ra-cha, la cu-ca-ra-cha, ya no pue-de ca-mi-nar, por-que no tie-ne, por-que le fal-ta las dos pa-ti-tas de~a-trás.
"F"C C F F A A | c2 A4 | "C7"c d c B A c | B2 G4 | C C E E G G | B2 G4 | c d c B A G | "F"A2 F4 |]
w: Di-cen que la cu-ca-ra-cha es un a-ni-mal pe-que-ño, y cuan-do~en-tra~en u-na ca-sa se tie-ne que que-dar due-ño.
\`\`\`

:::columns{count=2 gap=4mm}
:::verse{style="verse" align=start}
**2** Ya murió la cucaracha,
  ya la llevan a enterrar
entre cuatro zopilotes
  y un ratón de sacristán.
:::

:::verse{style="verse" align=start}
**3** Una cosa me da risa:
  Pancho Villa sin camisa;
ya se van los carrancistas
  porque vienen los villistas.
:::
:::

# Notas a las canciones {style="back"}

## Cumpleaños feliz

La melodía es la de *Good Morning to All*, que la compositora y maestra Mildred J. Hill escribió para el cancionero escolar *Song Stories for the Kindergarten*, publicado en Chicago en 1893 con letra de su hermana Patty. La letra española no tiene autor conocido. En el tercer verso, donde dice «to-dos», muchas familias cantan el nombre de quien cumple años: «te de-se-a-mos, Lu-cía».

## Martinillo

Es la melodía de *Frère Jacques*, que se canta en Francia desde el siglo XVIII y que en España y América se aprende con esta letra. Funciona como canon: el primer grupo empieza solo y, cuando llega al tercer compás, entra el segundo desde el principio; el tercero y el cuarto entran igual, cada dos compases. Cada grupo da la vuelta completa dos o tres veces y termina en su «din, don, dan», de modo que la última voz se queda sola.

## La cucaracha

Fernán Caballero ya recoge la canción en Sevilla en 1859, en sus *Cuentos y poesías populares andaluces*, y Francisco Rodríguez Marín publica más coplas en 1883. Cruzó a México, donde las tropas de la Revolución le pusieron letras nuevas hacia 1914; de ahí viene la tercera. Se canta el estribillo entre copla y copla, cada vez un poco más deprisa.

:::paragraphs{style="colophon"}
Compuesto en Spectral, Spectral SC y Fraunces (SIL Open Font License); música en Leland. Letras y melodías de dominio público. Ibarra y Soler es un sello inventado para este ejemplo.
:::
`; // content.<lang>.md, inlined by the Cookbook

// ─── 3 · Fonts ──────────────────────────────────────────────────────────────
const FONTS = {
  Spectral: ['400', '400i', '600', '700', '700i'], // 700: stanza numbers, the Chorus label
  'Spectral SC': ['400', '600'],
  Fraunces: ['600'],
};

// ─── 4 · Build & show ───────────────────────────────────────────────────────
await initMusicEngine(); // Verovio, from esm.sh: until it runs, a score is a grey box
const doc = await buildDocumentWithFonts({ markdown }, config, kitFonts(FONTS));
showPages(doc, { title: t({ en: 'Songs for the Long Table', es: 'Cancionero de la merienda' }) });
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 v3 ── the same in every recipe · postext.dev/cookbook
// Postext loads the faces a config asks for before layout (prepareFonts,
// buildDocumentWithFonts) and asks the kit for the files: Fontsource's, the
// files the PDF embeds too.

/** The options of buildDocumentWithFonts / prepareFonts / withLoadedFonts:
 *  the Fontsource resolver, and the faces of FONTS loaded with the ones the
 *  config names. FONTS = { 'Family Name': ['400', '400i', '700'] }. */
function kitFonts(FONTS = {}) {
  const faces = Object.entries(FONTS).flatMap(([family, specs]) => specs.map((spec) =>
    ({ family, weight: parseInt(spec, 10), style: spec.endsWith('i') ? 'italic' : 'normal' })));
  return { resolve: fontsourceResolver, faces };
}

/** A face's Fontsource files, in a fixed order: latin, then latin-ext and greek
 *  when `text` needs them and the family ships them (kitSubsetsFor), each with
 *  its unicode-range. Null for a weight or slant Fontsource does not ship. */
async function fontsourceResolver(family, weight, style, { text = '' } = {}) {
  const id = fontsourceId(family);
  const meta = await fontsourceMeta(family);
  if (meta && !(meta.weights.includes(weight) && meta.styles.includes(style))) return null;
  kitStatus('Loading fonts…');
  const at = await fontsourceAt(id);
  return ['latin', ...kitSubsetsFor(text, meta)].map((subset) => ({
    source: `${at}files/${id}-${subset}-${weight}-${style}.woff2`,
    unicodeRange: kitRanges()[subset],
  }));
}

/** The unicode-range of each Fontsource file the kit loads (a function: the
 *  kit is inlined after the pen's code, which runs before a const would be set). */
function kitRanges() {
  return {
    latin: 'U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,'
      + 'U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD',
    'latin-ext': 'U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,'
      + 'U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF',
    greek: 'U+0370-03FF',
  };
}

/** For the cjk, arabic and comics blocks, which add their own files: the
 *  faces of `faces` = { 'Family Name': ['400', '400i', '700'] }. `text` is the sample:
 *  č ł † α χ also load latin-ext and greek files (kitSubsetsFor). With
 *  `optional`, a face Fontsource does not ship is skipped instead of failing.
 *  The files are added once all have loaded, in the order asked (latin, then
 *  latin-ext, then greek), whatever order they arrive in: the last one added
 *  wins a character two files hold. Resolves to the number of faces added. */
async function loadFonts(faces, text = '', { optional = false } = {}) {
  kitStatus('Loading fonts…');
  const ranges = kitRanges();
  const jobs = [];
  for (const [family, specs] of Object.entries(faces)) {
    const id = fontsourceId(family);
    const todo = [...new Set(specs)].map((spec) => [parseInt(spec, 10), spec.endsWith('i') ? 'italic' : 'normal'])
      .filter(([weight, style]) => !hasFace(family, weight, style)); // before any await
    const at = fontsourceAt(id);
    const meta = optional || /[^\0-ÿ]/u.test(text) ? await fontsourceMeta(family) : null;
    const subsets = ['latin', ...kitSubsetsFor(text, meta)];
    for (const [weight, style] of todo) {
      if (optional && !(meta?.weights.includes(weight) && meta.styles.includes(style))) continue;
      for (const subset of subsets) {
        const url = `${await at}files/${id}-${subset}-${weight}-${style}.woff2`;
        const face = new FontFace(family, `url(${url}) format('woff2')`,
          { weight: String(weight), style, unicodeRange: ranges[subset] });
        jobs.push(face.load().catch(() => {
          if (subset === 'latin' && !optional) throw new Error(`Fontsource has no ${family} ${weight} ${style}`);
        }));
      }
    }
  }
  const ready = await Promise.all(jobs).catch((error) => { kitFail(error); throw error; });
  return ready.filter((face) => face && document.fonts.add(face)).length;
}

/** A loaded FontFace covers this family, weight and style (fonts.check() would
 *  also say yes for families nobody declared). */
function hasFace(family, weight, style) {
  for (const face of document.fonts) {
    if (face.status !== 'loaded' || face.style !== style) continue;
    if (face.family.replace(/^["']|["']$/g, '') !== family) continue;
    const [low, high = low] = face.weight.split(' ').map(Number);
    if (weight >= low && weight <= high) return true;
  }
  return false;
}

/** The files beyond latin `text` needs that `meta`'s family ships. */
function kitSubsetsFor(text, meta) {
  return [[/[Ā-˿ᴀ-ᶿḀ-ỿ†ℓⱠ-Ɀ꜠-ꟿ]/u, 'latin-ext'], [/[Ͱ-Ͽ]/u, 'greek']]
    .filter(([re, x]) => re.test(text) && meta?.subsets?.includes(x)).map(([, x]) => x);
}

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

/** A package's folder at the one version @5 names now (#766). */
function fontsourceAt(id, u = `https://cdn.jsdelivr.net/npm/@fontsource/${id}@`) {
  return (fontsourceAt.c ??= {})[id] ??= fetch(u + '5/package.json').then((r) => r.json())
    .then((p) => p.version || 5, () => 5).then((v) => `${u}${v}/`);
}

/** The family's Fontsource metadata (weights, styles, subsets), or null. */
function fontsourceMeta(family) {
  fontsourceMeta.cache ??= new Map();
  const id = fontsourceId(family);
  if (!fontsourceMeta.cache.has(id)) {
    fontsourceMeta.cache.set(id, fetch(`https://api.fontsource.org/v1/fonts/${id}`)
      .then((res) => (res.ok ? res.json() : null), () => null));
  }
  return fontsourceMeta.cache.get(id);
}

// ─── Kit · viewer v2 ── the same in every recipe · postext.dev/cookbook
/** The pages as spreads on a dark desk, page 1 alone, then verso | recto,
 *  each painted when it scrolls near. Other options go to renderPageToCanvas. */
function showPages(docs, { title, width = 460, ...render } = {}) {
  const root = viewer(title);
  const pages = [docs].flat().flatMap((doc) =>
    doc.pages.map((page) => ({ doc, page, n: (doc.pageIndexOffset ?? 0) + page.index })));
  const spreads = [];
  let verso = null;
  for (const p of pages) {
    if (p.n % 2 === 1) { if (verso) spreads.push([verso, null]); verso = p; }
    else { spreads.push([verso, p]); verso = null; }
  }
  if (verso) spreads.push([verso, null]);
  const density = Math.min(window.devicePixelRatio || 1, 2);
  showPages.painter?.disconnect();
  const painter = new IntersectionObserver((entries) => {
    for (const { isIntersecting, target } of entries) {
      if (!isIntersecting) continue;
      painter.unobserve(target);
      const { doc, page } = target.postext;
      renderPageToCanvas(page, doc, target, { ...render, scale: (width * density) / page.width });
    }
  }, { rootMargin: '800px' });
  showPages.painter = painter;
  root.replaceChildren(...spreads.map((pair) => {
    const spread = document.createElement('div');
    spread.className = 'pt-spread';
    for (const p of pair) {
      const figure = document.createElement('figure');
      if (p) {
        const label = p.page.pageLabel || String(p.n + 1);
        const canvas = document.createElement('canvas');
        canvas.postext = p;
        canvas.style.aspectRatio = `${p.page.width} / ${p.page.height}`;
        canvas.setAttribute('role', 'img');
        canvas.setAttribute('aria-label', `Page ${label}`);
        const folio = document.createElement('figcaption');
        folio.textContent = label;
        figure.append(canvas, folio);
        painter.observe(canvas);
      } else figure.className = 'pt-blank';
      spread.append(figure);
    }
    return spread;
  }));
  kitStatus(`${pages.length} ${pages.length === 1 ? 'page' : 'pages'}`);
  document.documentElement.dataset.postext = 'ready';
  return pages.length;
}

/** The desk, the bar and the error reporting, created once. */
function viewer(title) {
  if (!document.getElementById('pt-kit')) {
    document.head.insertAdjacentHTML('beforeend', `<style id="pt-kit">
      :root { color-scheme: dark; }
      body { margin: 0; background: #0e1014; color: #b9bcc4; font: 13px/1.45 system-ui, sans-serif; }
      #pt-bar { position: sticky; top: 0; z-index: 1; display: flex; flex-wrap: wrap; align-items: center;
        gap: 6px 16px; padding: 10px 16px; background: rgb(14 16 20 / .92); backdrop-filter: blur(6px);
        border-bottom: 1px solid #23262d; }
      #pt-bar strong { color: #f4f1ea; font-weight: 600; }
      #pt-actions { display: flex; gap: 12px; margin-left: auto; }
      #pt-actions a, #pt-actions button { color: #d8a21a; font: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
      #pages { display: grid; justify-items: center; gap: 48px; padding: 32px 16px 72px; }
      .pt-spread { display: flex; }
      .pt-spread figure { margin: 0; width: min(460px, 44vw); }
      .pt-spread canvas { display: block; width: 100%; background: #fff;
        box-shadow: 0 1px 2px rgb(0 0 0 / .5), 0 22px 44px -16px rgb(0 0 0 / .8); }
      .pt-spread figure:first-child canvas { box-shadow: inset -14px 0 14px -14px rgb(0 0 0 / .18), 0 1px 2px rgb(0 0 0 / .5), 0 22px 44px -16px rgb(0 0 0 / .8); }
      .pt-spread figcaption { margin-top: 10px; text-align: center; font: 600 10px/1 system-ui, sans-serif;
        letter-spacing: .18em; text-transform: uppercase; color: #6c7079; }
      .pt-blank { visibility: hidden; }
      @media (max-width: 760px) {
        .pt-spread { flex-direction: column; gap: 32px; }
        .pt-spread figure { width: min(460px, 92vw); }
        .pt-blank { display: none; }
      }
    </style>`);
    document.body.insertAdjacentHTML('afterbegin',
      '<header id="pt-bar"><strong id="pt-title"></strong><span id="pt-status" role="status"></span><span id="pt-actions"></span></header>');
    document.getElementById('pt-title').textContent = document.title || 'Postext';
    addEventListener('error', (event) => kitFail(event.error ?? event.message));
    addEventListener('unhandledrejection', (event) => kitFail(event.reason));
  }
  if (title) document.getElementById('pt-title').textContent = title;
  return document.getElementById('pages')
    ?? document.body.appendChild(Object.assign(document.createElement('main'), { id: 'pages' }));
}

function kitStatus(text) {
  viewer();
  document.getElementById('pt-status').textContent = text;
}

function kitFail(error) {
  document.documentElement.dataset.postext = 'error';
  kitStatus(`Error: ${error?.message ?? error}`);
}

// ─── Kit · pdf v3 ── the same in every recipe that exports a PDF
/** The Fontsource files the screen used, as TrueType, with their range and
 *  place: the nearest weight the family ships, upright with no italic;
 *  latin, then what its letters need (kitSubsetsFor). */
async function fontsourceProvider(family, weight, style, request) {
  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 text = String.fromCodePoint(...(request?.codePoints ?? []));
  const files = await Promise.all(['latin', ...kitSubsetsFor(text, meta)].map(async (subset, order) => {
    const res = await fetch(`${await fontsourceAt(id)}files/${id}-${subset}-${w}-${s}.woff2`);
    if (!res.ok) throw new Error(`Fontsource has no ${family} ${w} ${s} ${subset}`);
    return Object.assign(await decompressWoff2(new Uint8Array(await res.arrayBuffer())),
      { unicodeRange: kitRanges()[subset], order });
  }));
  return files.length === 1 ? files[0] : files;
}

/** A "Build the PDF" button; then "Open the PDF" (a new tab: CodePen's frame
 *  shows no 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

### Graba en Bravura

Bravura tiene cabezas de nota más gruesas y aire de plancha grabada; le va a un himnario compuesto en una letra más oscura.

```diff
-  musicFont: 'Leland', // lighter noteheads than Bravura, close to Spectral's colour
+  musicFont: 'Bravura',
```

### Numera los compases

Un coro ensaya por números de compás; imprime uno al comienzo de cada sistema.

```diff
-  barNumbers: 'none', // a table of singers counts verses, not bars
+  barNumbers: 'system',
```

## Errores frecuentes

- **Un estilo de título hereda el salto de página de su nivel.** Una entrada de headingStyles toma de su nivel de título todo lo que no fija, también breakBefore. Un índice o un colofón con estilo sobre un H1 tras un :::pagebreak hereda la paridad 'odd' y cae detrás de una página en blanco. Dale a ese estilo breakBefore: { enabled: false }.

- El lector ABC de Verovio no hace caso de las marcas `_` y `*` de una línea `w:`: cada nota toma la sílaba siguiente. Bajo la segunda nota de una ligadura escribe un espacio de no separación (U+00A0) como sílaba propia; no imprime nada. Las notas ligadas por `-` no toman sílaba.
- `~` une dos sílabas bajo una misma nota (`de~a-trás`), pero el grabador las imprime con un espacio normal, sin el signo de sinalefa.
- Escribe los acordes sin ♭ ni ♯ cuando puedas: la mayoría de las letras de texto no tienen esos signos y la página tira de una fuente del sistema. Elige tonalidades con acordes naturales (sol, do, re7, mi menor) o escribe «si bemol» en el texto.

## Créditos

- Receta: Ignacio Ferro ([@drnachio](https://github.com/drnachio))
- Texto: Amazing Grace: letra (1779, cinco de las seis estrofas, con la grafía de los Olney Hymns): John Newton ([fuente](https://en.wikipedia.org/wiki/Amazing_Grace)), dominio público
- Texto: Amazing Grace: la melodía New Britain (Southern Harmony, 1835), según la armonización de E. O. Excell de 1900, con cifrados añadidos: Traditional American; William Walker; Edwin O. Excell ([fuente](https://en.wikipedia.org/wiki/Amazing_Grace)), dominio público
- Texto: Oh My Darling, Clementine: letra y melodía (Oliver Ditson, Boston, 1884): Percy Montrose ([fuente](https://en.wikipedia.org/wiki/Oh_My_Darling,_Clementine)), dominio público
- Texto: Auld Lang Syne: letra (1788) y la melodía escocesa de Select Songs of Scotland de George Thomson (1799): Robert Burns; traditional ([fuente](https://en.wikipedia.org/wiki/Auld_Lang_Syne)), dominio público
- Texto: Cumpleaños feliz: la melodía de Good Morning to All (Song Stories for the Kindergarten, Chicago, 1893); letra española popular: Mildred J. Hill; traditional ([fuente](https://en.wikipedia.org/wiki/Happy_Birthday_to_You)), dominio público
- Texto: Martinillo: la melodía francesa de Frère Jacques (siglo XVIII) con la letra popular española: Traditional ([fuente](https://en.wikipedia.org/wiki/Fr%C3%A8re_Jacques)), dominio público
- Texto: La cucaracha: melodía, estribillo y copla según la transcripción de la Wikipedia en español; dos coplas populares mexicanas: Traditional ([fuente](https://es.wikipedia.org/wiki/La_cucaracha)), dominio público
- Texto: El prólogo, las notas a las canciones y los cifrados: Ignacio Ferro, CC-BY-4.0
- Tipografías: Spectral (OFL-1.1), Spectral SC (OFL-1.1), Fraunces (OFL-1.1)
- Código: MIT · Contenido de ejemplo: CC-BY-4.0

## Relacionadas

- [N.º 015 · Poemas compuestos verso a verso](https://postext.dev/es/cookbook/poetry-collection.md): Cada poema es un bloque :::verse escrito como se imprime: las sangrías de 1918 son espacios iniciales y los versos partidos cuelgan 2 em de su verso. · Nivel 2 (Intermedio) · Poesía
- [N.º 044 · Edición crítica: numeración de versos y notas por verso](https://postext.dev/es/cookbook/critical-edition-line-numbers.md): El Lycidas de Milton, texto de 1645: lineNumbers numera los versos de cinco en cinco en la columna lateral, y cada nota se abre con el número de su verso. · Nivel 3 (Avanzado) · Poesía
- [N.º 083 · Poemas Tang frente a su versión inglesa](https://postext.dev/es/cookbook/tang-poems-facing-english.md): Cada poema Tang abre una página impar teñida y la versión de Giles, la par de enfrente: un estilo de encabezado salta a página par y el otro a impar. · Nivel 2 (Intermedio) · Poesía
