# Le letture di un seminario da un export BibTeX di Zotero

> Le letture di un seminario con i riferimenti in un export BibTeX di Zotero incollato com’è, citato in Chicago autore-data, e nocite per le opere mai citate.

- Versione HTML: https://postext.dev/it/cookbook/zotero-bibtex-nocite
- Ricetta Nº 097 · Struttura del libro · Livello 2 (Intermedio) · Output: Canvas
- Generi: Libri di testo, Rapporti
- Richiede postext ≥ 1.25.0 · provata con 1.31.0 il 2026-10-13
- Pagine: [1](https://postext.dev/cookbook/zotero-bibtex-nocite/en/p01.webp?v=a4407678), [2](https://postext.dev/cookbook/zotero-bibtex-nocite/en/p02.webp?v=a4407678)
- Apri nella Sandbox: https://postext.dev/it/sandbox#recipe=zotero-bibtex-nocite&lang=en (.postext: https://postext.dev/cookbook/zotero-bibtex-nocite/en/zotero-bibtex-nocite.postext)
- Ultimo aggiornamento: 2026-10-10
- Altre lingue: [en](https://postext.dev/en/cookbook/zotero-bibtex-nocite.md), [es](https://postext.dev/es/cookbook/zotero-bibtex-nocite.md), [ca](https://postext.dev/ca/cookbook/zotero-bibtex-nocite.md), [de](https://postext.dev/de/cookbook/zotero-bibtex-nocite.md), [fr](https://postext.dev/fr/cookbook/zotero-bibtex-nocite.md), [pt](https://postext.dev/pt/cookbook/zotero-bibtex-nocite.md), [zh](https://postext.dev/zh/cookbook/zotero-bibtex-nocite.md), [ja](https://postext.dev/ja/cookbook/zotero-bibtex-nocite.md), [ar](https://postext.dev/ar/cookbook/zotero-bibtex-nocite.md)

## In breve

Le letture di un seminario universitario. Il docente incolla il file che esporta Zotero; Postext stampa le citazioni e un elenco che contiene anche libri che il testo non nomina.

## Che cosa comporrai

L’elenco delle letture di un seminario di sei settimane di storia e sociologia della scienza, composto su una pagina di 210 × 280 mm in due colonne. Un campo verde scuro occupa la metà superiore della prima pagina, con il titolo del corso in Spectral Light a 50 pt; il testo sotto è la guida del docente a ogni settimana. I riferimenti sono la collezione Zotero del seminario, esportata in BibTeX e incollata nel documento senza toccare una riga: le chiavi di citazione di Zotero, le sue graffe attorno alle maiuscole, i suoi accenti `{\"o}`, `month = dec`, i campi `file`, `keywords` e `urldate`. Il testo cita otto delle opere. Le altre cinque, letture di sfondo che il seminario non discute, entrano nell’elenco tramite `nocite`.

**Questa ricetta risponde a:**

- Come uso un export BibTeX di Zotero ed elenco opere che il testo non cita mai?
- Come cito le opere e costruisco la bibliografia in APA, IEEE o un altro stile citazionale?

## La risposta breve

```js
// script.js, righe 28–40
// The works come from a :::references{format=bibtex} block: the Zotero export pasted as it
// is. The text cites some of them; `nocite` in the front matter adds the rest by key, and
// `nocite: "@*"` would add the whole export. citeproc-js writes both in the style named.
registerCitationEngine(createCiteprocEngine({ styles: STYLES, locales: LOCALES }));
const citations = {
  style: 'chicago-author-date', // '(Gieryn 1983)', 'Shapin and Schaffer (1985)'
  link: true, // a citation jumps to its entry
  bibliography: {
    // includeUncited: true is the same as nocite: "@*", set from the design side.
    fontSize: em(0.94), lineHeight: pt(12.4), hangingIndent: mm(5), entrySpacing: pt(3.5),
    doi: 'link', // Zotero's doi field prints as https://doi.org/…; its url field is dropped
  },
};
```

## Ingredienti

**Insegna**

- [Bibliografia dai riferimenti](https://postext.dev/it/docs/document-format.md#citazioni-e-bibliografia): L’elenco delle opere citate, costruito dai riferimenti del documento (frontmatter o un blocco BibTeX) dove sta :::bibliography o dopo l’ultimo capitolo.
- [Citazioni in uno stile citazionale](https://postext.dev/it/docs/document-format.md#citazioni-e-bibliografia): Opere citate come [@key, p. 33] e formattate in uno stile CSL (APA, Chicago, MLA, IEEE, Vancouver, ISO 690, GB/T 7714…) scelto nelle impostazioni, collegate alle loro voci.

**Usa anche**

- [Aperture progettate](https://postext.dev/it/docs/configuration-text.md#estensione-e-progetto-grafico-avanzato)
- [Attributi dei titoli](https://postext.dev/it/docs/document-format.md#attributi-dei-titoli)
- [Una o due colonne](https://postext.dev/it/docs/configuration-page-layout.md#tipi-di-impaginazione)
- [Testatine e folio](https://postext.dev/it/docs/configuration-page-layout.md#intestazioni-e-piè-di-pagina)
- [Stili di paragrafo](https://postext.dev/it/docs/configuration-styles.md#stili-di-paragrafo)
- [Tavolozza di colori semantica](https://postext.dev/it/docs/configuration-fonts-colors-viewers.md#tavolozza-dei-colori)
- [Fascia di capitolo a tutta larghezza](https://postext.dev/it/docs/configuration-text.md#estensione-e-progetto-grafico-avanzato)
- [Testatine secondo il ruolo della pagina](https://postext.dev/it/docs/configuration-page-layout.md#elementi-di-testo)

**La configurazione in breve**

- [`bodyText`](https://postext.dev/it/docs/configuration-text.md#testo-corrente), [`citations`](https://postext.dev/it/docs/configuration-notes-references.md#citazioni), [`colorPalette`](https://postext.dev/it/docs/configuration-fonts-colors-viewers.md#tavolozza-dei-colori), [`footer`](https://postext.dev/it/docs/configuration-page-layout.md#intestazioni-e-piè-di-pagina), [`header`](https://postext.dev/it/docs/configuration-page-layout.md#intestazioni-e-piè-di-pagina), [`headings`](https://postext.dev/it/docs/configuration-text.md#titoli), [`layout`](https://postext.dev/it/docs/configuration-page-layout.md#impaginazione), [`locale`](https://postext.dev/it/docs/configuration-text.md#sillabazione), [`page`](https://postext.dev/it/docs/configuration-page-layout.md#pagina), [`paragraphStyles`](https://postext.dev/it/docs/configuration-styles.md#stili-di-paragrafo)

**API**

- [`LOCALES`](https://postext.dev/it/docs/document-format.md#citazioni-e-bibliografia), [`STYLES`](https://postext.dev/it/docs/document-format.md#citazioni-e-bibliografia), [`buildDocumentWithFonts`](https://postext.dev/it/docs/configuration-programmatic-usage.md#caricare-i-font-prima-dellimpaginazione), [`createCiteprocEngine`](https://postext.dev/it/docs/document-format.md#citazioni-e-bibliografia), [`registerCitationEngine`](https://postext.dev/it/docs/document-format.md#citazioni-e-bibliografia), [`renderPageToCanvas`](https://postext.dev/it/docs/configuration-programmatic-usage.md#rendere-una-pagina-come-bitmap)

**Caratteri**

- Spectral (OFL-1.1), IBM Plex Sans Condensed (OFL-1.1)

## Preparazione

### 1 · Nomina lo stile una volta

Il codice è nella [risposta breve](https://postext.dev/it/cookbook/zotero-bibtex-nocite.md#la-risposta-breve) qui sopra. Il motore delle citazioni si registra prima della prima composizione e lo stile è una sola impostazione: Chicago autore-data scrive «(Shapin 1984)» per `[@shapin_pump_1984]` e «Shapin and Schaffer (1985)» per `@shapin_leviathan_1985` dentro una frase. Le chiavi sono quelle che Zotero ha costruito dal primo autore, dalla prima parola significativa del titolo e dall’anno, così `gieryn_boundary-work_1983` mantiene il suo trattino; Postext legge un trattino o un trattino basso dentro una chiave come parte di essa.

### 2 · Incolla l’export come lo scrive Zotero

L’ultimo blocco del documento contiene l’export, una voce per opera, rientrata con tabulazioni come la scrive Zotero:

```bibtex
@article{gieryn_boundary-work_1983,
	title = {Boundary-{Work} and the {Demarcation} of {Science} from {Non}-{Science}: …},
	url = {https://www.jstor.org/stable/2095325},
	doi = {10.2307/2095325},
	urldate = {2026-08-21},
	journal = {American Sociological Review},
	author = {Gieryn, Thomas F.},
	month = dec,
	year = {1983},
	keywords = {demarcation, week 4},
	pages = {781--795},
	file = {JSTOR Full Text PDF:/Users/hps214/Zotero/storage/2HJ8M6PA/Gieryn - 1983 - ….pdf:application/pdf},
}
```

Il lettore toglie le graffe che proteggono le maiuscole, compone `{\"o}` e `{\'e}` in ö ed é, trasforma `--` in un trattino medio e legge `month = dec` come il nome di un mese senza virgolette. `urldate` diventa la data di consultazione e `language` la lingua della voce; `file`, `keywords`, `abstract` e `shorttitle` vengono saltati, così nulla del tuo disco arriva sulla pagina.

### 3 · Porta dentro le opere che il testo non cita

Il frontmatter le elenca per chiave:

```yaml
---
title: "Science as a Social Practice"
nocite: "@fleck_genesis_1979, @merton_sociology_1973, @latour_science_1987, @rheinberger_toward_1997, @lopez_pinero_ciencia_1979"
---
```

L’elenco contiene ogni opera citata più queste cinque, ordinate insieme per autore come chiede Chicago; nulla segna una voce come non citata. `nocite: "@*"` elenca l’intero export, e `bibliography.includeUncited: true` fa lo stesso dalla configurazione, per un progetto grafico che stampa sempre l’intera biblioteca.

### 4 · Un campo verde per la scheda

```js
// script.js, righe 44–68
const BAND = 132; // mm from the trim's top to the band's foot
const onBand = { color: col('paper'), align: 'left', overflow: 'wrap' };
const opener = {
  enabled: true,
  minHeight: mm(BAND - TOP + 9), // the columns start 9 mm under the band
  slot: { elements: [
    { kind: 'box', id: 'band', style: { backgroundColor: col('accent') },
      placement: { anchor: { to: 'page', edge: 'top-left' },
        size: { width: 'fill', height: mm(BAND) } } },
    { kind: 'text', id: 'kicker', content: '{attr.kicker}', ...onBand, fontFamily: LABEL,
      fontSize: pt(8.5), fontWeight: 600, letterSpacing: pt(1.7), textTransform: 'uppercase',
      placement: { anchor: { to: 'container', edge: 'top-left' }, offset: { y: mm(14) } } },
    { kind: 'text', id: 'title', content: '{titleText}', ...onBand, fontFamily: TEXT,
      fontSize: pt(50), fontWeight: 300, lineHeight: 1.02,
      placement: { anchor: { to: '#kicker', edge: 'below' }, offset: { y: mm(7) },
        size: { width: mm(150), height: 'auto' } } },
    { kind: 'rule', id: 'rule', direction: 'horizontal', thickness: pt(1), color: col('tint'),
      placement: { anchor: { to: '#title', edge: 'below' }, offset: { y: mm(7) },
        size: { width: mm(18) } } },
    { kind: 'text', id: 'lead', content: '{attr.lead}', ...onBand, fontFamily: TEXT,
      italic: true, fontSize: pt(13), lineHeight: 1.3, color: col('tint'),
      placement: { anchor: { to: '#rule', edge: 'below' }, offset: { y: mm(6) },
        size: { width: mm(128), height: 'auto' } } },
  ] },
};
```

La fascia è un riquadro ancorato al rifilo, alto 132 mm, e le due colonne cominciano 9 mm sotto. Il titolo, il filetto e il sommario sono concatenati con ancoraggi `below`, così un titolo del corso più lungo spinge in basso il sommario invece di sovrapporglisi.

```js
// script.js, righe 15–21
const palette = {
  ink: '#1c211f', accent: '#2d5b48', tint: '#dfe9e2', rule: '#c3cdc6', muted: '#5c6862',
  paper: '#ffffff',
};
const col = (id) => ({ hex: palette[id], model: 'hex', paletteId: id });
const colorPalette = Object.entries({ ...palette, 'main-color': palette.accent })
  .map(([id, hex]) => ({ id, name: id, value: { hex, model: 'hex' } }));
```

## La ricetta completa

Un unico file, composto a partire dalla cartella della ricetta con il testo di esempio e il kit comune del Ricettario già inclusi; costruisce la propria pagina. Per eseguirlo, mettilo in uno `<script type="module">` di una pagina vuota, oppure incollalo nel pannello JS di un nuovo pen di CodePen (come modulo). Importa postext da esm.sh: non c’è niente da installare né da compilare.

- Cartella della ricetta: https://github.com/drnachio/postext/tree/main/cookbook/zotero-bibtex-nocite

### script.js

```js
// ═══ Postext Cookbook · Nº 097 · A reading list from a Zotero BibTeX export ═══════
// https://postext.dev/en/cookbook/zotero-bibtex-nocite
// Code: MIT · Text: original (CC BY 4.0) · Pictures: none
// Fonts: Spectral, IBM Plex Sans Condensed (SIL OFL 1.1) · Needs postext ≥ 1.25.0
import {
  buildDocumentWithFonts, renderPageToCanvas, registerCitationEngine,
} from 'https://esm.sh/postext';
import { createCiteprocEngine, STYLES, LOCALES } from 'https://esm.sh/postext-citeproc';

const LANG = 'en'; // @lang: the language of the sample document ('en' | 'es')
const RECIPE = 'zotero-bibtex-nocite';

// ─── 1 · Design ─────────────────────────────────────────────────────────────
// #region palette: a department green, one tint, everything else in ink
const palette = {
  ink: '#1c211f', accent: '#2d5b48', tint: '#dfe9e2', rule: '#c3cdc6', muted: '#5c6862',
  paper: '#ffffff',
};
const col = (id) => ({ hex: palette[id], model: 'hex', paletteId: id });
const colorPalette = Object.entries({ ...palette, 'main-color': palette.accent })
  .map(([id, hex]) => ({ id, name: id, value: { hex, model: 'hex' } }));
// #endregion
const [TEXT, LABEL] = ['Spectral', 'IBM Plex Sans Condensed'];
const LEAD = 13.4; // pt
const [TOP, OUTER] = [24, 18]; // mm

// #region answer: Chicago author-date, and a list that holds more than the text cites
// The works come from a :::references{format=bibtex} block: the Zotero export pasted as it
// is. The text cites some of them; `nocite` in the front matter adds the rest by key, and
// `nocite: "@*"` would add the whole export. citeproc-js writes both in the style named.
registerCitationEngine(createCiteprocEngine({ styles: STYLES, locales: LOCALES }));
const citations = {
  style: 'chicago-author-date', // '(Gieryn 1983)', 'Shapin and Schaffer (1985)'
  link: true, // a citation jumps to its entry
  bibliography: {
    // includeUncited: true is the same as nocite: "@*", set from the design side.
    fontSize: em(0.94), lineHeight: pt(12.4), hangingIndent: mm(5), entrySpacing: pt(3.5),
    doi: 'link', // Zotero's doi field prints as https://doi.org/…; its url field is dropped
  },
};
// #endregion

// #region opener: a green field over the top half, the course in white on it
const BAND = 132; // mm from the trim's top to the band's foot
const onBand = { color: col('paper'), align: 'left', overflow: 'wrap' };
const opener = {
  enabled: true,
  minHeight: mm(BAND - TOP + 9), // the columns start 9 mm under the band
  slot: { elements: [
    { kind: 'box', id: 'band', style: { backgroundColor: col('accent') },
      placement: { anchor: { to: 'page', edge: 'top-left' },
        size: { width: 'fill', height: mm(BAND) } } },
    { kind: 'text', id: 'kicker', content: '{attr.kicker}', ...onBand, fontFamily: LABEL,
      fontSize: pt(8.5), fontWeight: 600, letterSpacing: pt(1.7), textTransform: 'uppercase',
      placement: { anchor: { to: 'container', edge: 'top-left' }, offset: { y: mm(14) } } },
    { kind: 'text', id: 'title', content: '{titleText}', ...onBand, fontFamily: TEXT,
      fontSize: pt(50), fontWeight: 300, lineHeight: 1.02,
      placement: { anchor: { to: '#kicker', edge: 'below' }, offset: { y: mm(7) },
        size: { width: mm(150), height: 'auto' } } },
    { kind: 'rule', id: 'rule', direction: 'horizontal', thickness: pt(1), color: col('tint'),
      placement: { anchor: { to: '#title', edge: 'below' }, offset: { y: mm(7) },
        size: { width: mm(18) } } },
    { kind: 'text', id: 'lead', content: '{attr.lead}', ...onBand, fontFamily: TEXT,
      italic: true, fontSize: pt(13), lineHeight: 1.3, color: col('tint'),
      placement: { anchor: { to: '#rule', edge: 'below' }, offset: { y: mm(6) },
        size: { width: mm(128), height: 'auto' } } },
  ] },
};
// #endregion

const head = (id, content, parity, edge, x, extra = {}) => ({
  kind: 'text', id, content, parity, pages: 'body', fontFamily: LABEL, fontSize: pt(7.5),
  fontWeight: 600, letterSpacing: pt(1.2), textTransform: 'uppercase', color: col('muted'),
  placement: { anchor: { to: 'page', edge }, offset: { x: mm(x), y: mm(13) } }, ...extra,
});
const folio = { color: col('accent') };
const listName = t({ en: 'HPS 214 · Reading list', es: 'HPS 214 · Lecturas' });

const config = () => ({
  locale: t({ en: 'en-us', es: 'es' }),
  colorPalette,
  citations,
  page: {
    sizePreset: 'custom', width: mm(210), height: mm(280), dpi: 150,
    margins: { top: mm(TOP), bottom: mm(22), left: mm(20), right: mm(OUTER), mirror: true },
  },
  layout: { layoutType: 'double', gutterWidth: mm(7) },
  bodyText: {
    fontFamily: TEXT, fontSize: pt(9.6), lineHeight: pt(LEAD), color: col('ink'),
    boldColor: col('ink'), italicColor: col('ink'), referenceColor: col('accent'),
    textAlign: 'justify', firstLineIndent: mm(4), indentAfterHeading: false,
    hyphenation: { enabled: true }, optimalLineBreaking: true,
    avoidWidows: true, avoidOrphans: true, avoidRunts: true,
  },
  headings: {
    fontFamily: TEXT, color: col('ink'), fontWeight: 600,
    levels: [
      // parity 'any': the H1 opens on the next page, either side (the default waits for a recto).
      { level: 1, span: 'page', breakBefore: { enabled: true, parity: 'any' },
        marginBottom: pt(0), advancedDesign: opener },
      { level: 2, fontFamily: LABEL, fontSize: pt(10), lineHeight: pt(LEAD), fontWeight: 600,
        color: col('accent'), marginTop: pt(LEAD), marginBottom: pt(0),
      },
    ],
  },
  paragraphStyles: [
    { id: 'colophon', fontFamily: LABEL, fontSize: pt(7), lineHeight: pt(9.5),
      color: col('muted'), textAlign: 'left', firstLineIndent: pt(0), marginTop: pt(LEAD) },
  ],
  header: { elements: [
    head('verso-folio', '{pageNumber}', 'even', 'top-left', OUTER, folio),
    head('verso-title', '{title}', 'even', 'top-left', OUTER + 8),
    head('recto-title', listName, 'odd', 'top-right', -(OUTER + 8)),
    head('recto-folio', '{pageNumber}', 'odd', 'top-right', -OUTER, folio),
  ] },
  footer: { elements: [] },
});

// ─── 2 · Content ────────────────────────────────────────────────────────────
const markdown = String.raw`---
title: "Science as a Social Practice"
nocite: "@fleck_genesis_1979, @merton_sociology_1973, @latour_science_1987, @rheinberger_toward_1997, @lopez_pinero_ciencia_1979"
---

# Science as a Social Practice {kicker="HPS 214 · Seminar reading list · Autumn term 2026" lead="Six weeks on how scientific knowledge is made, tested and trusted, from the paradigm to the laboratory notebook."}

This seminar reads the history and sociology of science as one subject. Its question is how a claim about nature becomes a fact that people who never saw the experiment are willing to rely on. The answers we read come from historians of the seventeenth century, from sociologists who sat in laboratories for two years, and from philosophers who looked at what scientists actually do rather than at what their textbooks say they do.

Each week has one set reading, which everyone reads before the seminar, and one or two companion texts for the person introducing the discussion. The set readings are available in the course reader; the others are in the library’s reserve collection.

## Week 1 · Paradigms and normal science

We start with the book that made the subject fashionable. Read the first five sections and the Postscript of @kuhn_structure_1970, which Kuhn added for the second edition to answer the charge that “paradigm” meant too many things at once. His preface credits a monograph by Ludwik Fleck, written in 1935 and nearly forgotten until Kuhn mentioned it; it is in the list at the end, and the student introducing the week should read its second chapter.

## Week 2 · Witnessing an experiment

The set reading is the first two chapters of @shapin_leviathan_1985, on Robert Boyle’s air-pump and the dispute with Thomas Hobbes over whether an experiment could settle anything. The companion text is the shorter article in which Shapin first described Boyle’s “literary technology”, the way a report written in plain, circumstantial prose turned its readers into witnesses [@shapin_pump_1984].

## Week 3 · Inside the laboratory

Bruno Latour spent two years at the Salk Institute as an observer of a neuroendocrinology laboratory. Read the second and third chapters of @latour_laboratory_1979 and follow how a statement loses its qualifications (“it seems that”, “X has claimed”) as it travels from one paper to the next until it is printed without any.

## Week 4 · Where science ends

The demarcation problem asks what separates science from what is not science. Philosophers looked for a criterion; @gieryn_boundary-work_1983 looked at the people who draw the line in practice, and at what they gain by drawing it where they do. Bring an example from the news of the last month.

## Week 5 · Objectivity has a history

Read the article first [@daston_image_1992], then the first chapter and the chapter on “trained judgment” in the book it grew into [@daston_objectivity_2007]. The argument is that objectivity, as a virtue of the scientist, appeared in the nineteenth century, and that atlases of skeletons, snowflakes and clouds show it arriving.

## Week 6 · Whom to believe

The last week returns to the seventeenth century with a broader question: how did gentlemen-philosophers decide whose reports to accept? Read the first chapter of @shapin_social_1994 and come ready to compare its answer with how you decide whom to believe when you read a paper outside your field.

## Further reading

The list below is the seminar’s Zotero collection, exported as BibTeX and set as it came out of Zotero. It includes every work cited above and five that the seminar does not discuss but that you will meet in the essays you read: Fleck’s monograph, Merton’s collected essays on the norms of science, Latour’s later handbook, Rheinberger’s study of experimental systems, and a history of Spanish science in the sixteenth and seventeenth centuries for those who read Spanish.

:::bibliography{title=""}

:::paragraphs{style="colophon"}
Set in Spectral and IBM Plex Sans Condensed (SIL OFL) · Reading list and notes: original, CC BY 4.0 · The works are real; the Zotero file paths and access dates are illustrative.
:::

:::references{format=bibtex}

@book{kuhn_structure_1970,
	address = {Chicago},
	edition = {2},
	title = {The {Structure} of {Scientific} {Revolutions}},
	abstract = {Normal science, anomaly and crisis, and the paradigm changes that end them, with the Postscript of 1969.},
	language = {en},
	publisher = {University of Chicago Press},
	author = {Kuhn, Thomas S.},
	year = {1970},
	keywords = {paradigms, week 1},
	file = {Kuhn - 1970 - The Structure of Scientific Revolutions.pdf:/Users/hps214/Zotero/storage/7QW2KD9N/Kuhn - 1970 - The Structure of Scientific Revolutions.pdf:application/pdf},
}

@book{fleck_genesis_1979,
	address = {Chicago},
	title = {Genesis and {Development} of a {Scientific} {Fact}},
	language = {en},
	publisher = {University of Chicago Press},
	author = {Fleck, Ludwik},
	editor = {Trenn, Thaddeus J. and Merton, Robert K.},
	translator = {Bradley, Fred and Trenn, Thaddeus J.},
	year = {1979},
	keywords = {thought collectives, week 1},
}

@book{shapin_leviathan_1985,
	address = {Princeton, NJ},
	title = {Leviathan and the {Air}-{Pump}: {Hobbes}, {Boyle}, and the {Experimental} {Life}},
	shorttitle = {Leviathan and the {Air}-{Pump}},
	language = {en},
	publisher = {Princeton University Press},
	author = {Shapin, Steven and Schaffer, Simon},
	year = {1985},
	keywords = {experiment, week 2},
}

@article{shapin_pump_1984,
	title = {Pump and {Circumstance}: {Robert} {Boyle}'s {Literary} {Technology}},
	volume = {14},
	shorttitle = {Pump and {Circumstance}},
	url = {https://doi.org/10.1177/030631284014004001},
	doi = {10.1177/030631284014004001},
	language = {en},
	number = {4},
	urldate = {2026-08-21},
	journal = {Social Studies of Science},
	author = {Shapin, Steven},
	month = nov,
	year = {1984},
	keywords = {literary technology, week 2},
	pages = {481--520},
	file = {Shapin - 1984 - Pump and Circumstance.pdf:/Users/hps214/Zotero/storage/V3NC8RTE/Shapin - 1984 - Pump and Circumstance.pdf:application/pdf},
}

@book{latour_laboratory_1979,
	address = {Beverly Hills},
	title = {Laboratory {Life}: {The} {Social} {Construction} of {Scientific} {Facts}},
	shorttitle = {Laboratory {Life}},
	language = {en},
	publisher = {Sage},
	author = {Latour, Bruno and Woolgar, Steve},
	year = {1979},
	keywords = {ethnography, week 3},
}

@book{latour_science_1987,
	address = {Cambridge, MA},
	title = {Science in {Action}: {How} to {Follow} {Scientists} and {Engineers} through {Society}},
	shorttitle = {Science in {Action}},
	language = {en},
	publisher = {Harvard University Press},
	author = {Latour, Bruno},
	year = {1987},
}

@book{rheinberger_toward_1997,
	address = {Stanford, CA},
	title = {Toward a {History} of {Epistemic} {Things}: {Synthesizing} {Proteins} in the {Test} {Tube}},
	language = {en},
	publisher = {Stanford University Press},
	author = {Rheinberger, Hans-J{\"o}rg},
	year = {1997},
	keywords = {experimental systems},
}

@article{gieryn_boundary-work_1983,
	title = {Boundary-{Work} and the {Demarcation} of {Science} from {Non}-{Science}: {Strains} and {Interests} in {Professional} {Ideologies} of {Scientists}},
	volume = {48},
	shorttitle = {Boundary-{Work} and the {Demarcation} of {Science} from {Non}-{Science}},
	url = {https://www.jstor.org/stable/2095325},
	doi = {10.2307/2095325},
	language = {en},
	number = {6},
	urldate = {2026-08-21},
	journal = {American Sociological Review},
	author = {Gieryn, Thomas F.},
	month = dec,
	year = {1983},
	keywords = {demarcation, week 4},
	pages = {781--795},
	file = {JSTOR Full Text PDF:/Users/hps214/Zotero/storage/2HJ8M6PA/Gieryn - 1983 - Boundary-Work and the Demarcation of Science from Non-Science.pdf:application/pdf},
}

@article{daston_image_1992,
	title = {The {Image} of {Objectivity}},
	url = {https://www.jstor.org/stable/2928741},
	doi = {10.2307/2928741},
	language = {en},
	number = {40},
	urldate = {2026-08-22},
	journal = {Representations},
	author = {Daston, Lorraine and Galison, Peter},
	year = {1992},
	keywords = {objectivity, week 5},
	pages = {81--128},
	file = {JSTOR Full Text PDF:/Users/hps214/Zotero/storage/F9XK4C2B/Daston and Galison - 1992 - The Image of Objectivity.pdf:application/pdf},
}

@book{daston_objectivity_2007,
	address = {New York},
	title = {Objectivity},
	language = {en},
	publisher = {Zone Books},
	author = {Daston, Lorraine and Galison, Peter},
	year = {2007},
	keywords = {objectivity, week 5},
}

@book{shapin_social_1994,
	address = {Chicago},
	series = {Science and {Its} {Conceptual} {Foundations}},
	title = {A {Social} {History} of {Truth}: {Civility} and {Science} in {Seventeenth}-{Century} {England}},
	language = {en},
	publisher = {University of Chicago Press},
	author = {Shapin, Steven},
	year = {1994},
	keywords = {trust, week 6},
}

@book{merton_sociology_1973,
	address = {Chicago},
	title = {The {Sociology} of {Science}: {Theoretical} and {Empirical} {Investigations}},
	language = {en},
	publisher = {University of Chicago Press},
	author = {Merton, Robert K.},
	editor = {Storer, Norman W.},
	year = {1973},
}

@book{lopez_pinero_ciencia_1979,
	address = {Barcelona},
	title = {Ciencia y t{\'e}cnica en la sociedad espa{\~n}ola de los siglos {XVI} y {XVII}},
	language = {es},
	publisher = {Labor},
	author = {L{\'o}pez Pi{\~n}ero, Jos{\'e} Mar{\'i}a},
	year = {1979},
}
:::
`;

// ─── 3 · Fonts ──────────────────────────────────────────────────────────────
const FONTS = {
  Spectral: ['300', '300i', '400', '400i', '600', '600i'],
  'IBM Plex Sans Condensed': ['400', '600'],
};

// ─── 4 · Build & show ───────────────────────────────────────────────────────
const doc = await buildDocumentWithFonts({ markdown }, config(), kitFonts(FONTS));
showPages(doc, { title: t({ en: 'A reading list from Zotero', es: 'Lecturas desde Zotero' }) });

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

## Varianti

### Elenca tutta la collezione Zotero

Quando l’export è l’elenco, salta le chiavi: si stampa ogni voce del blocco, citata o no.

```diff
-nocite: "@fleck_genesis_1979, @merton_sociology_1973, @latour_science_1987, …"
+nocite: "@*"
```

### Cambia stile

Le stesse chiavi danno un elenco Harvard (Cite Them Right) senza nessun’altra modifica.

```diff
-  style: 'chicago-author-date', // '(Gieryn 1983)', 'Shapin and Schaffer (1985)'
+  style: 'harvard-cite-them-right',
```

## Errori frequenti

- **Una chiave nocite che non corrisponde a nulla viene scartata in silenzio.** Una chiave citata senza riferimento genera unknownCitationKey, ma una chiave in nocite che non corrisponde ad alcuna voce viene saltata senza avviso. Copia le chiavi dall’esportazione e conta le voci nell’elenco.

- Una chiave in `nocite` che non corrisponde a nessuna voce viene scartata senza avviso. Copia le chiavi dall’export invece di digitarle, e conta le voci sulla pagina.
- Zotero costruisce le chiavi dai dati: correggi il nome di un autore o un titolo in Zotero e l’export successivo può cambiare la chiave, il che rompe ogni sua citazione. Fissa le chiavi prima di scrivere il testo: Zotero legge una riga `Citation Key: …` nel campo Extra di una voce.
- Zotero esporta in UTF-8 per impostazione predefinita, con gli accenti così come sono. Il blocco qui è stato esportato con l’opzione di codifica occidentale, che scrive `{\"o}`; Postext legge entrambi.
- La 18ª edizione di Chicago omette il luogo di pubblicazione, quindi il campo `address` non si stampa, e un articolo con un DOI stampa il DOI e non l’`url` di JSTOR.

## Crediti

- Ricetta: Ignacio Ferro ([@drnachio](https://github.com/drnachio))
- Caratteri: Spectral (OFL-1.1), IBM Plex Sans Condensed (OFL-1.1)
- Codice: MIT · Contenuto di esempio: CC-BY-4.0

## Ricette correlate

- [Nº 087 · Un capitolo di tesi con citazioni APA 7](https://postext.dev/it/cookbook/apa-thesis-with-bibtex.md): Un capitolo di dottorato le cui citazioni [@key, p. 33] diventano APA 7 con citeproc-js, con la bibliografia costruita da un blocco BibTeX. · Livello 2 (Intermedio) · Articoli scientifici e testi accademici, Rapporti
- [Nº 093 · Un testo in quattro stili di citazione](https://postext.dev/it/cookbook/one-text-four-citation-styles.md): Un breve saggio con sei citazioni, costruito in APA 7, Chicago autore-data, IEEE e ISO 690: l’impostazione dello stile è l’unica cosa che cambia. · Livello 2 (Intermedio) · Articoli scientifici e testi accademici, Rapporti
- [Nº 073 · Un indice dei nomi e un indice degli argomenti](https://postext.dev/it/cookbook/names-and-subjects-indexes.md): I segni con index="names" formano l’indice delle persone, gli altri quello degli argomenti; :::index li stampa su due colonne, la costruzione fissa le pagine. · Livello 2 (Intermedio) · Libri di testo
