# Un dictamen jurídic citat en OSCOLA

> Un comentari de sentència de quatre pàgines les cites del qual [@robinson2018, para 21] surten com a notes OSCOLA: casos en cursiva, cita neutra, ibid i (n 1).

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

## En poques paraules

Un comentari breu sobre una sentència anglesa relativa a la policia. Cada sentència, llei o article en què es basa es cita en una nota numerada a peu de pàgina, amb l'estil de les facultats de dret britàniques.

## Què compondràs

Una separata de la sèrie de comentaris de sentència d'una revista jurídica: quatre pàgines sobre *Robinson v Chief Constable of West Yorkshire Police*, la sentència del Tribunal Suprem britànic del 2018 sobre quan la policia té un deure de diligència. La pàgina fa 162 × 240 mm, amb marges en mirall, en Libre Baskerville de 9,4/13,6 pt, i la primera s'obre amb una banda bordeus i el nom del cas en cursiva de 60 pt. L'autora escriu cada font com una clau amb el seu localitzador, `[@caparo1990, 617–18]`, i Postext compon 23 notes a peu de pàgina en OSCOLA, la norma de cita de les facultats de dret britàniques: casos en cursiva, lleis en rodona, sense punt després d'AC, HL o edn, *ibid* per a la font de la nota anterior i *Robinson (n 1) [55]* després. Les resolucions són reals; l'autora i la revista, no.

**Aquesta recepta respon a:**

- Com cito sentències i lleis en notes a peu de pàgina amb l'estil OSCOLA?
- Com poso les cites en notes a peu de pàgina, a l'estil de Chicago, amb forma breu en les següents?

## La resposta curta

```js
// script.js, línies 50–68
// The text cites with [@robinson2018, para 21]; OSCOLA is a note style, so each citation
// becomes a footnote: the case in full the first time, "ibid" when the note before cites
// the same authority, "Robinson (n 1) [55]" after that. Case names come out italic,
// statutes roman, and no abbreviation takes a full stop (AC, HL, UKSC, edn, n, s).
registerCitationEngine(createCiteprocEngine({ styles: STYLES, locales: LOCALES }));
const citations = {
  style: 'custom', customStyle: OSCOLA, // the bundled 'oscola', patched above
  locale: 'en-GB', // OSCOLA's words stay English in the Spanish edition too
  notes: 'footnote',
  link: false, // a case comment has no bibliography to link to
  bibliography: { auto: false }, // OSCOLA articles cite in the notes only
};
// The citation notes share the numbering and the look of the author's own [^notes].
const footnotes = {
  placement: 'column', numbering: 'chapter',
  fontSize: pt(7.8), lineHeight: pt(10.2), spaceBetween: pt(2.2),
  textAlign: 'left', // ragged: long report citations would open wide gaps if justified
  separator: { width: 0.25, lineWidth: pt(0.5), color: col('rule') },
};
```

## Ingredients

**Ensenya**

- [Cites en notes](https://postext.dev/ca/docs/document-format.md#citacions-i-bibliografia): Un estil de notes (Chicago notes, OSCOLA, GB/T 7714 notes) posa cada cita en una nota a peu de pàgina, amb forma breu a les cites següents, o en una nota de dues línies dins de la línia xinesa.
- [Cites en un estil de cita](https://postext.dev/ca/docs/document-format.md#citacions-i-bibliografia): Obres citades com a [@clau, pàg. 33] i formatades en un estil CSL (APA, Chicago, MLA, IEEE, Vancouver, ISO 690, GB/T 7714…) triat a la configuració, enllaçades amb les seves entrades.

**També fa servir**

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

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

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

**API**

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

**Tipus de lletra**

- Libre Baskerville (OFL-1.1), Libre Caslon Display (OFL-1.1), Libre Franklin (OFL-1.1)

## Elaboració

### 1 · Una nota OSCOLA per a cada font

El codi és [la resposta curta](#la-resposta-curta) de més amunt. OSCOLA és un estil de notes: `[@robinson2018, para 21]` deixa un número volat després del punt i el seu text passa al peu. La nota 1 dona el cas complet, *Robinson v Chief Constable of West Yorkshire Police [2018] UKSC 4, [2018] AC 736*; la nota 2, que el torna a citar tot seguit, diu *ibid [21]*; la nota 8, amb altres casos pel mig, *Robinson (n 1) [26–27]*. Un article de revista jurídica cita només en notes, així que `bibliography.auto: false` deixa la llista d'obres fora de l'última pàgina. Les paraules de l'estil continuen en anglès en aquesta edició (`locale: 'en-GB'`), com en una traducció que conserva l'aparat original.

### 2 · Un cas és una dada, escrita com la imprimeixen els repertoris

Un cas és un element CSL de `type: legal_case`: el nom a `title`, el tribunal a `authority`, la sèrie del repertori a `container-title` i la primera pàgina a `page`. OSCOLA posa l'any d'un repertori entre claudàtors quan l'any identifica el volum, com als Appeal Cases, així que la recepta escriu aquest any a `volume`, seguit del número de volum: `volume: "[1990] 2"` dona *[1990] 2 AC 605 (HL)*. Un cas posterior al 2001 té a més cita neutra: `authority: UKSC`, `number: "4"` i `issued: 2018` donen *[2018] UKSC 4*, i darrere va el repertori. Una llei és `type: legislation`, amb l'any al títol i sense `issued`, perquè l'any no surti dues vegades, i `[@hra1998, § 6]` cita un article com a *s 6*.

### 3 · Dos retocs a l'estil inclòs

```js
// script.js, línies 32–46
// The bundled oscola.csl writes a paragraph pinpoint in brackets only in a case's first
// note: a later note printed "Robinson (n 1) para 55" and "ibid 27", and a repeated
// statute "ibid 7". These two replacements give "Robinson (n 1) [55]", "ibid [27]" and
// "ibid s 7", as OSCOLA prints them.
const LOCATOR = `<macro name="label-locator"><choose>
  <if type="legal_case" locator="paragraph">
    <text variable="locator" prefix="[" suffix="]"/></if>
  <else-if locator="page"><text variable="locator"/></else-if>
  <else><group delimiter=" "><label form="short" strip-periods="true" variable="locator"/>
    <text variable="locator"/></group></else>
</choose></macro>`;
const OSCOLA = STYLES.oscola
  .replace(/<macro name="label-locator">[\s\S]*?<\/macro>/, LOCATOR)
  .replace(/(<if position="ibid-with-locator">[\s\S]*?)<text variable="locator"\/>/,
    '$1<text macro="label-locator"/>');
```

L'`oscola.csl` inclòs posa entre claudàtors el paràgraf citat només a la primera nota d'un cas. Les següents imprimien *para 55* i *ibid 27*, i una segona nota a la mateixa llei perdia la *s*. El retoc canvia una macro i una línia de l'XML de l'estil abans de compondre i el passa com a estil propi, sense tocar el text ni les referències.

### 4 · El nom del cas a la banda

```js
// script.js, línies 72–96
const BAND = 124; // mm from the trim's top
const big = (id, content, family, size, color, y, extra = {}) => ({
  kind: 'text', id, content, fontFamily: family, fontSize: pt(size), lineHeight: 1.08, color,
  align: 'left', overflow: 'wrap', ...extra,
  placement: { ...at('container', 'top-left', 0, y), size: { width: mm(118), height: 'auto' } },
});
const opener = {
  enabled: true,
  minHeight: mm(BAND - MARGIN.top + 8), // the text starts 8 mm under the band
  slot: { elements: [
    { kind: 'box', id: 'band', style: { backgroundColor: col('claret') },
      placement: { ...at('page', 'top-left'), size: { width: 'fill', height: mm(BAND) } } },
    big('kicker', '{attr.kicker}', LABEL, 7.4, col('tint'), 2,
      { fontWeight: 600, letterSpacing: pt(1.4), textTransform: 'uppercase' }),
    big('case', '{attr.case}', TEXT, 60, col('paper'), 9, { italic: true }),
    big('parties', '{attr.parties}', TEXT, 12.5, col('tint'), 33, { italic: true }),
    big('citation', '{attr.citation}', TEXT, 10.5, col('tint'), 40),
    { kind: 'box', id: 'hairline', style: { backgroundColor: col('tint') },
      placement: { ...at('container', 'top-left', 0, 51),
        size: { width: mm(30), height: pt(0.6) } } },
    big('title', '{titleText}', DISPLAY, 27, col('paper'), 57),
    big('byline', '{attr.byline}', LABEL, 8.5, col('tint'), 82,
      { fontWeight: 500, letterSpacing: pt(0.5) }),
  ] },
};
```

La banda baixa 124 mm des del tall. El nom del cas, les parts i la cita són atributs del títol, així que un altre comentari de la sèrie només canvia el seu Markdown. *Robinson* va en Libre Baskerville cursiva de 60 pt perquè OSCOLA posa en cursiva els noms dels casos, i la cita de sota continua en rodona, com a les notes.

```js
// script.js, línies 16–23
const palette = {
  ink: '#1f1b1a', claret: '#5e1b26', tint: '#ecdcd8', rule: '#bfb4ad', muted: '#6b625d',
  paper: '#fcfaf6',
};
// 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.claret })
  .map(([id, hex]) => ({ id, name: id, value: { hex, model: 'hex' } }));
```

## La recepta completa

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

- Carpeta de la recepta: https://github.com/drnachio/postext/tree/main/cookbook/legal-opinion-oscola

### script.js

```js
// ═══ Postext Cookbook · Nº 099 · A legal opinion cited in OSCOLA ══════════════════
// https://postext.dev/en/cookbook/legal-opinion-oscola
// Code: MIT · Text: original (CC BY 4.0) · Pictures: none
// Fonts: Libre Baskerville, Libre Caslon Display, Libre Franklin (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 = 'legal-opinion-oscola';

// ─── 1 · Design ─────────────────────────────────────────────────────────────
// #region palette: a law-report claret for the band, ink and cream paper for the rest
const palette = {
  ink: '#1f1b1a', claret: '#5e1b26', tint: '#ecdcd8', rule: '#bfb4ad', muted: '#6b625d',
  paper: '#fcfaf6',
};
// 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.claret })
  .map(([id, hex]) => ({ id, name: id, value: { hex, model: 'hex' } }));
// #endregion
const TEXT = 'Libre Baskerville', DISPLAY = 'Libre Caslon Display', LABEL = 'Libre Franklin';
const TRIM = { width: 162, height: 240 }; // a law-journal offprint
const MARGIN = { top: 23, bottom: 22, inner: 19, outer: 21 };
const LEAD = 13.6; // pt
const at = (to, edge, x = 0, y = 0) => ({ anchor: { to, edge }, offset: { x: mm(x), y: mm(y) } });

// #region oscola: two patches to the bundled OSCOLA style, applied before the build
// The bundled oscola.csl writes a paragraph pinpoint in brackets only in a case's first
// note: a later note printed "Robinson (n 1) para 55" and "ibid 27", and a repeated
// statute "ibid 7". These two replacements give "Robinson (n 1) [55]", "ibid [27]" and
// "ibid s 7", as OSCOLA prints them.
const LOCATOR = `<macro name="label-locator"><choose>
  <if type="legal_case" locator="paragraph">
    <text variable="locator" prefix="[" suffix="]"/></if>
  <else-if locator="page"><text variable="locator"/></else-if>
  <else><group delimiter=" "><label form="short" strip-periods="true" variable="locator"/>
    <text variable="locator"/></group></else>
</choose></macro>`;
const OSCOLA = STYLES.oscola
  .replace(/<macro name="label-locator">[\s\S]*?<\/macro>/, LOCATOR)
  .replace(/(<if position="ibid-with-locator">[\s\S]*?)<text variable="locator"\/>/,
    '$1<text macro="label-locator"/>');
// #endregion

// #region answer: OSCOLA footnotes for cases, statutes and articles
// The text cites with [@robinson2018, para 21]; OSCOLA is a note style, so each citation
// becomes a footnote: the case in full the first time, "ibid" when the note before cites
// the same authority, "Robinson (n 1) [55]" after that. Case names come out italic,
// statutes roman, and no abbreviation takes a full stop (AC, HL, UKSC, edn, n, s).
registerCitationEngine(createCiteprocEngine({ styles: STYLES, locales: LOCALES }));
const citations = {
  style: 'custom', customStyle: OSCOLA, // the bundled 'oscola', patched above
  locale: 'en-GB', // OSCOLA's words stay English in the Spanish edition too
  notes: 'footnote',
  link: false, // a case comment has no bibliography to link to
  bibliography: { auto: false }, // OSCOLA articles cite in the notes only
};
// The citation notes share the numbering and the look of the author's own [^notes].
const footnotes = {
  placement: 'column', numbering: 'chapter',
  fontSize: pt(7.8), lineHeight: pt(10.2), spaceBetween: pt(2.2),
  textAlign: 'left', // ragged: long report citations would open wide gaps if justified
  separator: { width: 0.25, lineWidth: pt(0.5), color: col('rule') },
};
// #endregion

// #region opener: the claret band holds the case name, its citation and the title
const BAND = 124; // mm from the trim's top
const big = (id, content, family, size, color, y, extra = {}) => ({
  kind: 'text', id, content, fontFamily: family, fontSize: pt(size), lineHeight: 1.08, color,
  align: 'left', overflow: 'wrap', ...extra,
  placement: { ...at('container', 'top-left', 0, y), size: { width: mm(118), height: 'auto' } },
});
const opener = {
  enabled: true,
  minHeight: mm(BAND - MARGIN.top + 8), // the text starts 8 mm under the band
  slot: { elements: [
    { kind: 'box', id: 'band', style: { backgroundColor: col('claret') },
      placement: { ...at('page', 'top-left'), size: { width: 'fill', height: mm(BAND) } } },
    big('kicker', '{attr.kicker}', LABEL, 7.4, col('tint'), 2,
      { fontWeight: 600, letterSpacing: pt(1.4), textTransform: 'uppercase' }),
    big('case', '{attr.case}', TEXT, 60, col('paper'), 9, { italic: true }),
    big('parties', '{attr.parties}', TEXT, 12.5, col('tint'), 33, { italic: true }),
    big('citation', '{attr.citation}', TEXT, 10.5, col('tint'), 40),
    { kind: 'box', id: 'hairline', style: { backgroundColor: col('tint') },
      placement: { ...at('container', 'top-left', 0, 51),
        size: { width: mm(30), height: pt(0.6) } } },
    big('title', '{titleText}', DISPLAY, 27, col('paper'), 57),
    big('byline', '{attr.byline}', LABEL, 8.5, col('tint'), 82,
      { fontWeight: 500, letterSpacing: pt(0.5) }),
  ] },
};
// #endregion

const head = (id, content, parity, edge, x, align) => ({
  kind: 'text', id, content, parity, pages: 'body', fontFamily: LABEL, fontWeight: 500,
  fontSize: pt(7.2), letterSpacing: pt(1), textTransform: 'uppercase', color: col('muted'),
  align, placement: at('page', edge, x, 13),
});
const journal = t({ en: 'Notes on Obligations', es: 'Cuadernos de Obligaciones' });

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,
  footnotes,
  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(9.4), lineHeight: pt(LEAD), color: col('ink'),
    boldColor: col('ink'), italicColor: col('ink'), referenceColor: col('ink'),
    textAlign: 'justify', firstLineIndent: mm(4.5), indentAfterHeading: false,
    hyphenation: { enabled: true }, optimalLineBreaking: true,
    avoidWidows: true, avoidOrphans: true, avoidRunts: true,
  },
  headings: {
    fontFamily: TEXT, fontWeight: 400, color: col('ink'),
    levels: [
      // Restated: any headings object drops the H1 break (gotcha: headings-drop-h1-break).
      { level: 1, fontSize: pt(27), breakBefore: { enabled: true, parity: 'any' },
        advancedDesign: opener },
      { level: 2, italic: true, fontSize: pt(12), color: col('claret'),
        lineHeight: pt(LEAD), marginTop: pt(LEAD), marginBottom: pt(LEAD / 2) },
    ],
  },
  paragraphStyles: [
    { id: 'colophon', fontFamily: LABEL, fontSize: pt(6.8), lineHeight: pt(9.2),
      color: col('muted'), textAlign: 'left', firstLineIndent: pt(0), marginTop: pt(LEAD) },
  ],
  header: { elements: [
    head('verso-folio', '{pageNumber}', 'even', 'top-left', MARGIN.outer, 'left'),
    head('verso-journal', journal, '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 on the opener page only
    { kind: 'text', id: 'drop-folio', content: '{pageNumber}', pages: 'opener',
      fontFamily: LABEL, fontWeight: 500, fontSize: pt(7.2), color: col('muted'),
      align: 'center', placement: at('page', 'bottom', 0, -11) },
  ] },
});

// ─── 2 · Content ────────────────────────────────────────────────────────────
const markdown = String.raw`---
title: "Acción, omisión y policía"
author: "Harriet Okafor"
---

# Acción, omisión y policía {#comment kicker="Cuadernos de Obligaciones · Comentario" case="Robinson" parties="v Chief Constable of West Yorkshire Police" citation="[2018] UKSC 4, [2018] AC 736" byline="Harriet Okafor"}

Una tarde de julio de 2008, dos agentes de paisano intentaron detener a un hombre sospechoso de vender droga en una calle comercial de Huddersfield. El hombre se resistió, y los tres cayeron sobre la señora Robinson, una mujer de 76 años que pasaba por allí, y la tiraron al suelo. El juez de instancia declaró que los agentes habían actuado con negligencia al intentar la detención allí y entonces, pero sostuvo que la policía no tenía un deber de diligencia frente al transeúnte herido en una operación contra el delito. El Tribunal de Apelación le dio la razón. El Tribunal Supremo, no.[@robinson2018]

Lord Reed, a cuyo voto se adhirieron Lady Hale y Lord Hodge, empezó por corregir una lectura del derecho que llevaba casi treinta años sin corregirse.[@robinson2018, para 21] Este comentario sostiene que la sentencia importa menos por lo que decidió sobre la policía que por el método que restableció. El tribunal que se pregunta si existe un deber de diligencia debe buscar primero una categoría ya reconocida y razonar por analogía a partir de ella; las consideraciones de política jurídica solo entran cuando el caso es nuevo.

## No existe un «test de Caparo»

El derecho moderno arranca de la fórmula de Lord Atkin: hay que poner un cuidado razonable para evitar los actos u omisiones que uno puede prever razonablemente que dañarán a su prójimo.[@donoghue1932, 580] En los años setenta, la Cámara de los Lores quiso convertir esa frase en una regla general. Lord Wilberforce propuso dos pasos: una relación de proximidad en la que el daño fuera previsible y, después, cualquier consideración que debiera negar o limitar el deber.[@anns1978, 751–52] La regla extendió la responsabilidad más deprisa de lo que los tribunales podían justificar, y *Anns* quedó revocada en 1990.[@murphy1991]

Ese mismo año, Lord Bridge describió los elementos del deber: la previsibilidad del daño, una relación de proximidad y una situación en la que al tribunal le parezca justo y razonable imponerlo.[@caparo1990, 617–18] Muchos jueces lo aplicaron después como un test de tres pasos a cualquier demanda, incluidas las de daños corporales causados por un acto positivo. Lord Bridge había dicho lo contrario: dudaba de que una sola prueba pudiera resolver todos los casos y prefería que el derecho abriera categorías nuevas paso a paso.[^brennan]

[^brennan]: Lord Bridge hizo suyas las palabras del juez Brennan en el Tribunal Supremo de Australia; véase [@caparo1990, 618; @sutherland1985, 481]

*Robinson* recupera esa lectura. Cuando un caso entra en una categoría reconocida, el tribunal aplica el derecho establecido; la pregunta por lo justo y razonable solo surge cuando se le pide ir más allá.[@robinson2018, para 26–27] El daño corporal causado por un acto positivo negligente es una categoría reconocida desde *Donoghue*. Detener a alguien en una calle llena de gente era un acto de ese tipo, el riesgo para quienes estaban cerca era previsible y no hacía falta nada más para fundar el deber.[@robinson2018, para 74]

## Actos y omisiones

El *common law* no impone, en general, el deber de proteger a otro del daño que causa un tercero o la naturaleza.[@smith1987, 271] Lord Hoffmann dio la razón en términos políticos: obligar a alguien a tener cuidado cuando decide actuar invade menos su libertad que obligarlo a actuar, y un deber de socorro plantea por qué ha de recaer en un transeúnte y no en otro.[@stovin1996, 943–44] La regla tiene excepciones: que el demandado haya asumido la responsabilidad, controle la fuente del peligro o lo haya creado.

Leídos así, los casos anteriores sobre la policía eran casos de omisión. En *Hill*, la madre de la última víctima de Peter Sutcliffe demandó a la policía que no había sabido detenerlo. La Cámara de los Lores sostuvo que no había deber alguno frente a su hija, que formaba parte de un público indeterminado, y añadió razones de política sobre el efecto de la responsabilidad en el trabajo policial.[@hill1989, 63] Lord Reed trató esas razones como un complemento de la regla ordinaria: la policía, como cualquiera, no tiene el deber de impedir el daño que causa un tercero, salvo que se dé alguna excepción; pero sí tiene el deber ordinario de no lesionar a nadie con sus propios actos.[@robinson2018, para 55]

La distinción ya había decidido *Michael*, donde una llamada al teléfono de emergencias se clasificó mal y la mujer que llamaba murió antes de que llegaran los agentes. La mayoría sostuvo que la policía no había asumido responsabilidad por su seguridad y que, por tanto, no le debía diligencia.[@michael2015, para 97–100] En la doctrina, una postura sostiene que un organismo público creado para proteger a las personas debería tener un deber estrecho cuando conoce una amenaza concreta contra una persona concreta;[^peripheral] la otra, que la regla de las omisiones se aplica a las autoridades públicas igual que a los particulares y que cualquier deber más amplio corresponde al Parlamento.[@nolan2011]

[^peripheral]: [@tofaris2016, 130–32; cf @stapleton1995, 304–06, sobre el efecto disuasorio de demandar a quien no es el autor principal del daño]

## La vía legal

El artículo 6(1) de la Human Rights Act 1998 declara ilícito que una autoridad pública actúe de forma incompatible con un derecho del Convenio,[@hra1998, § 6] y la víctima puede demandarla conforme al artículo 7.[@hra1998, § 7] En *Michael*, la demanda basada en el artículo 2 del Convenio pudo llegar a juicio aunque la de negligencia fracasara.[@michael2015, para 139] En *DSD*, el Tribunal Supremo condenó a la policía conforme al artículo 3 por fallos graves en la investigación de una serie de agresiones sexuales cometidas por un taxista.[@dsd2018, para 24]

El temor de *Hill* a una policía que actúe a la defensiva tiene su propia respuesta legal. Cuando un tribunal decide qué medidas debió tomar el demandado para cumplir el estándar de diligencia, puede tener en cuenta si exigirlas impediría una actividad deseable.[@compensation2006, § 1] Es una cuestión de incumplimiento, no de deber.

## Comentario

Un año después, el Tribunal Supremo aplicó el mismo método a un ayuntamiento que no había realojado a una familia acosada por sus vecinos, y no apreció deber alguno: no había creado el peligro ni asumido la seguridad de los niños.[@poole2019, para 64–65]

Quedan dos dificultades. La línea entre acción y omisión es fácil de enunciar y difícil de trazar: el agente que abre una puerta y deja entrar a un hombre violento ha actuado; el que no la cierra con llave, no, y para quien está al otro lado la diferencia puede ser mínima. La asunción de responsabilidad puede nacer de una promesa, de una práctica o de un servicio, y tras *Robinson* los tribunales no se ponen de acuerdo sobre cuándo apreciarla.[^generally] La sentencia no las resuelve, pero las lleva al análisis de las categorías reconocidas.

[^generally]: Sobre las excepciones y la jurisprudencia de cada una, véase en general [@winfield2020]

:::paragraphs{style="colophon"}
Escrito para el Recetario de Postext (CC BY 4.0). Autora y revista ficticias; no es asesoramiento jurídico. Las resoluciones son reales; algunas referencias son ilustrativas. Compuesto en Libre Baskerville, Libre Caslon Display y Libre Franklin (SIL OFL).
:::

:::references{format=csl-yaml}
- id: donoghue1932
  type: legal_case
  title: Donoghue v Stevenson
  title-short: Donoghue
  volume: "[1932]"
  container-title: AC
  page: "562"
  authority: HL
- id: anns1978
  type: legal_case
  title: Anns v Merton London Borough Council
  title-short: Anns
  volume: "[1978]"
  container-title: AC
  page: "728"
  authority: HL
- id: murphy1991
  type: legal_case
  title: Murphy v Brentwood District Council
  title-short: Murphy
  volume: "[1991] 1"
  container-title: AC
  page: "398"
  authority: HL
- id: caparo1990
  type: legal_case
  title: Caparo Industries plc v Dickman
  title-short: Caparo
  volume: "[1990] 2"
  container-title: AC
  page: "605"
  authority: HL
- id: sutherland1985
  type: legal_case
  title: Sutherland Shire Council v Heyman
  title-short: Sutherland
  volume: "157"
  container-title: CLR
  page: "424"
  authority: HCA
  issued: 1985
- id: smith1987
  type: legal_case
  title: Smith v Littlewoods Organisation Ltd
  title-short: Smith
  volume: "[1987]"
  container-title: AC
  page: "241"
  authority: HL
- id: stovin1996
  type: legal_case
  title: Stovin v Wise
  title-short: Stovin
  volume: "[1996]"
  container-title: AC
  page: "923"
  authority: HL
- id: hill1989
  type: legal_case
  title: Hill v Chief Constable of West Yorkshire
  title-short: Hill
  volume: "[1989]"
  container-title: AC
  page: "53"
  authority: HL
- id: michael2015
  type: legal_case
  title: Michael v Chief Constable of South Wales Police
  title-short: Michael
  authority: UKSC
  number: "2"
  issued: 2015
  volume: "[2015]"
  container-title: AC
  page: "1732"
- id: robinson2018
  type: legal_case
  title: Robinson v Chief Constable of West Yorkshire Police
  title-short: Robinson
  authority: UKSC
  number: "4"
  issued: 2018
  volume: "[2018]"
  container-title: AC
  page: "736"
- id: dsd2018
  type: legal_case
  title: Commissioner of Police of the Metropolis v DSD
  title-short: DSD
  authority: UKSC
  number: "11"
  issued: 2018
  volume: "[2019]"
  container-title: AC
  page: "196"
- id: poole2019
  type: legal_case
  title: N v Poole Borough Council
  title-short: Poole
  authority: UKSC
  number: "25"
  issued: 2019
  volume: "[2020]"
  container-title: AC
  page: "780"
- id: hra1998
  type: legislation
  title: Human Rights Act 1998
- id: compensation2006
  type: legislation
  title: Compensation Act 2006
- id: stapleton1995
  type: article-journal
  author: [{family: Stapleton, given: Jane}]
  title: "Duty of Care: Peripheral Parties and Alternative Opportunities for Deterrence"
  container-title: LQR
  volume: "111"
  page: "301"
  issued: 1995
- id: nolan2011
  type: article-journal
  author: [{family: Nolan, given: Donal}]
  title: The Liability of Public Authorities for Failing to Confer Benefits
  container-title: LQR
  volume: "127"
  page: "260"
  issued: 2011
- id: tofaris2016
  type: article-journal
  author: [{family: Tofaris, given: Stelios}, {family: Steel, given: Sandy}]
  title: Negligence Liability for Omissions and the Police
  container-title: CLJ
  volume: "75"
  page: "128"
  issued: 2016
- id: winfield2020
  type: book
  author: [{family: Goudkamp, given: James}, {family: Nolan, given: Donal}]
  title: Winfield and Jolowicz on Tort
  edition: 20
  publisher: Sweet & Maxwell
  issued: 2020
:::
`;

// ─── 3 · Fonts ──────────────────────────────────────────────────────────────
// Every face the design uses (gotcha: fonts-first).
const FONTS = {
  'Libre Baskerville': ['400', '400i', '700'],
  'Libre Caslon Display': ['400'],
  'Libre Franklin': ['400', '500', '600'],
};

// ─── 4 · Build & show ───────────────────────────────────────────────────────
await loadFonts(FONTS, markdown);
const doc = await buildWithFonts(() => buildDocument({ markdown }, config()), markdown);
const title = t({ en: 'A legal opinion cited in OSCOLA',
  es: 'Un dictamen jurídico citado en OSCOLA' });
showPages(doc, { title });
offerPdf(() => renderToPdf(doc, { fontProvider: fontsourceProvider }), `${RECIPE}.pdf`);

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

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

// ─── Kit · fonts v1 ── the same in every recipe · postext.dev/cookbook ────────
// Postext measures text with the faces the browser has loaded, and caches the
// widths, so every face must be ready before the first build. Faces come from
// Fontsource: the same static files the PDF embeds, so screen and PDF agree.

/** faces = { 'Family Name': ['400', '400i', '700'] }. `text` is the sample:
 *  letters beyond Latin-1 (č, ł, ő…) also load the latin-ext files. With
 *  `optional`, a face Fontsource does not ship is skipped instead of failing.
 *  Resolves to the number of faces added. */
async function loadFonts(faces, text = '', { optional = false } = {}) {
  kitStatus('Loading fonts…');
  const ranges = {
    latin: 'U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,'
      + 'U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD',
    'latin-ext': 'U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,'
      + 'U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF',
  };
  const subsets = /[Ā-˿Ḁ-ỿ]/.test(text) ? ['latin', 'latin-ext'] : ['latin'];
  const jobs = [];
  let added = 0;
  for (const [family, specs] of Object.entries(faces)) {
    const id = fontsourceId(family);
    const meta = optional ? await fontsourceMeta(family) : null;
    for (const spec of new Set(specs)) {
      const weight = parseInt(spec, 10);
      const style = spec.endsWith('i') ? 'italic' : 'normal';
      if (hasFace(family, weight, style)) continue;
      if (optional && !(meta?.weights.includes(weight) && meta.styles.includes(style))) continue;
      for (const subset of subsets) {
        const url = `https://cdn.jsdelivr.net/npm/@fontsource/${id}@5/files/${id}-${subset}-${weight}-${style}.woff2`;
        const face = new FontFace(family, `url(${url}) format('woff2')`,
          { weight: String(weight), style, unicodeRange: ranges[subset] });
        jobs.push(face.load().then((ready) => { document.fonts.add(ready); added++; }, () => {
          if (subset === 'latin' && !optional) throw new Error(`Fontsource has no ${family} ${weight} ${style}`);
        }));
      }
    }
  }
  await Promise.all(jobs).catch((error) => { kitFail(error); throw error; });
  return added;
}

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

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

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

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

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

/** The weights and styles a family ships ({ weights: [400, 700], styles: ['normal', 'italic'] }), or null. */
function fontsourceMeta(family) {
  fontsourceMeta.cache ??= new Map();
  const id = fontsourceId(family);
  if (!fontsourceMeta.cache.has(id)) {
    fontsourceMeta.cache.set(id, fetch(`https://api.fontsource.org/v1/fonts/${id}`)
      .then((res) => (res.ok ? res.json() : null), () => null));
  }
  return fontsourceMeta.cache.get(id);
}

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

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

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

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

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

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

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

## Variants

### Fes servir l'estil tal com ve

Per comparar amb la sortida sense retocar, fes servir l'identificador inclòs. Els paràgrafs de les notes següents sortiran com a *para 55*.

```diff
-  style: 'custom', customStyle: OSCOLA, // the bundled 'oscola', patched above
+  style: 'oscola',
```

### Aplega les notes al final

Algunes revistes imprimeixen les notes després del text. Les cites conserven les formes completa i breu i el seu número.

```diff
-  placement: 'column', numbering: 'chapter',
+  placement: 'chapterEnd', numbering: 'chapter',
```

## Errors freqüents

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

- Un repertori el volum del qual és un número sol, `volume: "2"`, imprimeix l'any entre parèntesis, *(1990) 2 AC*, que OSCOLA reserva per a sèries numerades al llarg dels anys, com els Commonwealth Law Reports (nota 7). Als Appeal Cases i als Weekly Law Reports escriu l'any entre claudàtors dins de `volume`.
- `s` davant d'un localitzador vol dir pàgina (la *Seite* alemanya), no article: escriu `§ 6`. Un apartat com `6(1)` no es llegeix com a localitzador; anomena'l a la frase i cita l'article.
- OSCOLA no porta bibliografia en un article, i l'estil no separa taules de casos i de lleis. Una tesi que les necessiti obté de `:::bibliography` una sola llista ordenada, amb la doctrina primer i després casos i lleis barrejats.

## Crèdits

- Recepta: Ignacio Ferro ([@drnachio](https://github.com/drnachio))
- Tipus de lletra: Libre Baskerville (OFL-1.1), Libre Caslon Display (OFL-1.1), Libre Franklin (OFL-1.1)
- Codi: MIT · Contingut d'exemple: CC-BY-4.0

## Relacionades

- [Núm. 089 · Un assaig d'història amb notes de Chicago](https://postext.dev/ca/cookbook/history-essay-chicago-notes.md): Un assaig d'història de quatre pàgines on les citacions [@clau, 87] surten com a notes de Chicago a peu de pàgina: completes la primera vegada, breus després. · Nivell 2 (Intermedi) · Articles i treballs acadèmics
- [Núm. 096 · Un assaig d'humanitats amb obres citades en MLA](https://postext.dev/ca/cookbook/mla-humanities-essay.md): Un assaig literari amb cites [@clau, 48] que surten en MLA 9, autor i pàgina, amb una cita en bloc i la llista d'obres citades presa de dades CSL-YAML. · Nivell 2 (Intermedi) · Articles i treballs acadèmics
- [Núm. 095 · Un article mèdic en estil Vancouver](https://postext.dev/ca/cookbook/medical-article-vancouver.md): Un assaig clínic en A4 a dues columnes: números de cita volats, una bibliografia numerada a la manera de Vancouver i DOI que s'obren com a enllaços al PDF. · Nivell 2 (Intermedi) · Articles i treballs acadèmics
