Skip to content

Antikythera ​

The Antikythera client queries articles, annotations, journal settings, and custom fonts, and coordinates annotation highlights with the web components. Instantiate it in the browser and call the methods below.

INFO

Please contact Antikythera to get the correct 'entry' slug for your article.

javascript
import { Antikythera } from '@antikythera/antikythera'
import '@antikythera/antikythera/fonts'
const antikythera = new Antikythera({ entry: 'example-entry' })

init() ​

By default, the Antikythera class automatically initializes the antikythera-menu Web Component and crawls the DOM for .annotation tagged elements.

You can override this behavior by passing the manual: true parameter to the Antikythera class and then calling the init() method manually:

javascript
const antikythera = new Antikythera({ entry: 'example-entry', manual: true })

// wait 2 seconds before initializing:
setTimeout(() => {
	antikythera.init()
}, 2000)

When manually initializing the antikythera-menu, your optional naming overrides are passed directly to the init() method:

javascript
antikythera.init({
	menuName: 'antikythera-menu', // should match the name you've specified in the Antikythera class
	annotationClass: '.annotation',
	detectAnnotationsOnInit: true // set false here if for some reason you want to call detectAnnotations() method at a later point in time
})

init() Options ​

NameTypeRequiredUsageDefault
menuNameStringfalseName of Antikythera Menu Web Componentantikythera-menu
annotationClassStringfalseName of Selector Class used to find inline annotations.annotation
detectAnnotationsOnInitBooleanfalseScan the current body and begin observing the configured annotation class.true

reinit() ​

When a site switches between Antikythera articles, reinit() updates the current entry and reruns initialization. After reinitialization it dispatches antikythera:initComplete on document; event.detail.entry contains the new slug.

javascript
const antikythera = new Antikythera({ entry: 'example-entry' })

document.addEventListener('antikythera:initComplete', (e) => {
	console.log('reinitialisation complete')
	console.log(e.detail.entry) // <-- latest entry
	// do further processing / cleanup here...
})

setTimeout(() => {
	antikythera.reinit('other-entry')
}, 3000)

reinit() Options ​

NameTypeRequiredUsageDefault
entryStringtrueName of the new antikythera entry to useundefined
menuNameStringfalseName of Antikythera Menu Web Componentantikythera-menu
annotationClassStringfalseName of Selector Class used to find inline annotations.annotation
detectAnnotationsOnInitBooleanfalseScan the current body and begin observing the configured annotation class.true

detectAnnotations() ​

This method scans the document body for elements with the configured annotation class, applies the package's highlight metadata/styles, and connects them to the menu. It also starts a document observer: client-side pagination, route transitions, and DOM moves trigger a queued rescan, while already-marked highlights are adopted without wrapping their text again. Multiple connected elements may share one annotation ID and are tracked independently.

javascript
<span id="my-annotation" class="annotation">my highlighted phrase</span>

init() calls this method by default. That initial call also starts the document observer, so later pagination or route-driven DOM changes are handled automatically. Call it manually when automatic detection was disabled, or when application code needs to await an immediate rescan after replacing article content (for example, a language switch).

javascript
const toggleLanguageToEnglish = async () => {
	langIsEng.value = true
	await antikythera.detectAnnotations()
}
const toggleLanguageToFrench = async () => {
	langIsEng.value = false
	await antikythera.detectAnnotations()
}

getEntry() ​

javascript
const article = await antikythera.getEntry()

The response includes a top-level about object sourced from the journal document (about text, aside text, and contributors), plus the entry's pdf, markdown, and a pdfPreview with a computed 520px-wide preview url. Related entries include introduction and shortIntroduction descriptions plus their own pdf, markdown, and pdfPreview; the official menu intentionally offers Markdown export only for the current entry.

In v2.1, full responses also include top-level custom-font metadata (fonts, fontFaceCss). Entry-level globalFontSettings, tertiary-content controls, and media-item invertImagesOnHover flags pass through for article-template consumers. Boolean opt-ins are emitted only when true; revealTertiaryContentOnAnchorHover also coalesces the hidden legacy click-reveal field for backward compatibility.

Response ​

Details
json
{
	"settings": {
		"externalLinks": [
			{
				"linkTitle": "instagram",
				"linkUrl": "https://www.instagram.com"
			}
		],
		"shortDescription": "Journal of Philosophy of Planetary Computation.",
		"blurb": "Journal of Philosophy of Planetary Computation."
	},
	"about": {
		"text": "About the journal...",
		"asideText": "Aside text shown next to the about section...",
		"credits": [
			{
				"title": "Editors",
				"externalTitle": "Editorial",
				"creditLine": [
					{
						"name": "Firstname Lastname",
						"title": "Editor"
					}
				]
			}
		]
	},
	"fonts": [
		{
			"title": "Example Sans",
			"slug": "example-sans",
			"cssFamily": "example-sans",
			"files": [
				{
					"url": "https://cdn.sanity.io/files/y9ih15q4/production/example-sans.woff2",
					"weight": 400,
					"style": "normal",
					"filename": "example-sans.woff2",
					"format": "woff2"
				}
			]
		}
	],
	"fontFaceCss": "@font-face{font-family:'example-sans';font-style:normal;font-weight:400;font-display:swap;src:url('https://cdn.sanity.io/files/y9ih15q4/production/example-sans.woff2') format('woff2');}",
	"entry": {
		"doi": "123456789",
		"globalFontSettings": "example-sans",
		"revealTertiaryContentOnAnchorHover": true,
		"alignAllTertiaryContentToParagraphStart": true,
		"designers": [
			{
				"title": "Firstname Lastname",
				"externalLink": null,
				"featuredImage": {
					"url": null,
					"alt": null,
					"caption": null
				},
				"biography": null
			}
		],
		"featuredImage": {
			"url": "https://cdn.sanity.io/images/y9ih15q4/production/fe543c33bd7f472edd9d40b7c899e637033e609b-3000x2024.jpg",
			"alt": "a very religious scene",
			"caption": null
		},
		"credits": "Yuri Bultheel",
		"releaseDate": "2024-09-04",
		"bgColor": "#266fff",
		"textColor": "#ffffff",
		"theme": 1,
		"authors": [
			{
				"title": "Philip Maughan",
				"externalLink": null,
				"featuredImage": {
					"url": null,
					"alt": null,
					"caption": null
				},
				"biography": null
			}
		],
		"annotations": [
			{
				"featuredImage": {
					"url": "https://cdn.sanity.io/images/y9ih15q4/production/fe543c33bd7f472edd9d40b7c899e637033e609b-3000x2024.jpg",
					"alt": "religious scene",
					"caption": "a painful scene"
				},
				"id": "example-annotation",
				"title": "Example Annotation",
				"doi": "example DOI",
				"content": "Just some text about a certain annotation...",
				"annotationType": "commentary",
				"externalLink": "https://www.google.com"
			},
			{
				"externalLink": "https://www.dataexcess.xyz",
				"featuredImage": {
					"url": "https://cdn.sanity.io/images/y9ih15q4/production/fe543c33bd7f472edd9d40b7c899e637033e609b-3000x2024.jpg",
					"alt": null,
					"caption": "good picture"
				},
				"id": "second-annotation",
				"title": "A second Annotation",
				"doi": null,
				"content": "This is another annotation...",
				"annotationType": "related"
			}
		],
		"title": "Example Entry",
		"introduction": "Just an introduction to the article.\n\n\n\nPart 1.\n\nSome more text.\n\nPart 2.\n\nSome more text.\n\n\n\nFinal words.",
		"bibliography": "start of bibliography.\n\n\n\nthis is actual portable text by the way....\n\n\n\nend of bibliography.",
		"pdf": {
			"url": "https://cdn.sanity.io/files/y9ih15q4/production/example-entry.pdf",
			"pdfMimeType": "application/pdf",
			"pdfFilename": "example-entry.pdf"
		},
		"markdown": {
			"url": "https://cdn.sanity.io/files/y9ih15q4/production/example-entry.txt",
			"markdownMimeType": "text/plain",
			"markdownFilename": "example-entry.txt"
		},
		"pdfPreview": {
			"altText": null,
			"crop": null,
			"hotspot": null,
			"asset": {
				"_id": "image-abc123-1200x1553-png",
				"url": "https://cdn.sanity.io/images/y9ih15q4/production/example-preview.png",
				"metadata": {
					"dimensions": {
						"aspectRatio": 0.7727
					}
				}
			},
			"override": false,
			"downloadLabel": null,
			"downloadUrl": null,
			"url": "https://cdn.sanity.io/images/y9ih15q4/production/example-preview.png?w=520&auto=format"
		},
		"relatedEntries": [
			{
				"title": "A Related Entry",
				"url": "related-entry",
				"doi": "123456789",
				"doiUrl": "https://doi.org/123456789",
				"releaseDate": "2024-10-04",
				"introduction": "A related article introduction.",
				"shortIntroduction": "A shorter description for the related article card.",
				"externalLink": null,
				"featuredImage": {
					"url": "https://cdn.sanity.io/images/y9ih15q4/production/related.jpg",
					"alt": "related image",
					"caption": null
				},
				"featuredImageSquare": {
					"url": "https://cdn.sanity.io/images/y9ih15q4/production/related-square.jpg",
					"alt": "related square image"
				},
				"authors": [
					{
						"title": "Firstname Lastname",
						"externalLink": null
					}
				],
				"designers": [],
				"pdf": {
					"url": "https://cdn.sanity.io/files/y9ih15q4/production/related-entry.pdf",
					"pdfMimeType": "application/pdf",
					"pdfFilename": "related-entry.pdf"
				},
				"markdown": {
					"url": "https://cdn.sanity.io/files/y9ih15q4/production/related-entry.md",
					"markdownMimeType": "text/markdown",
					"markdownFilename": "related-entry.md"
				},
				"pdfPreview": {
					"asset": {
						"_id": "image-def456-1200x1553-png",
						"url": "https://cdn.sanity.io/images/y9ih15q4/production/related-preview.png",
						"metadata": {
							"dimensions": {
								"aspectRatio": 0.7727
							}
						}
					},
					"override": false,
					"downloadLabel": null,
					"downloadUrl": null,
					"url": "https://cdn.sanity.io/images/y9ih15q4/production/related-preview.png?w=520&auto=format"
				}
			}
		]
	}
}

v2.1 field paths ​

The feature flags used by article templates live at these paths:

PathEmission ruleMeaning
fonts[] / fontFaceCssTop-level on full entry responsesAvailable custom font families and generated document-level CSS.
entry.globalFontSettingsPassed through when definedEntry-wide font-family default; block-level settings may override it.
entry.revealTertiaryContentOnAnchorHoverPresent only as trueReveal anchored tertiary content on pointer hover or keyboard focus; also reflects the legacy click field.
entry.alignAllTertiaryContentToParagraphStartPresent only as trueOverride all individual tertiary alignment toggles.
entry.content[].carousel[].invertImagesOnHoverPresent only as trueInvert that carousel image on hover.

Advanced ​

Parameters

By default, getEntry() returns Portable Text for any richtext content. The getEntry() method exposes an optional parameter to return a String or raw HTML instead:

javascript
const articleWithStrings = await antikythera.getEntry({ textStyle: 'string' })

const articleWithHtml = await antikythera.getEntry({ textStyle: 'html' })
NameTypeRequiredUsageDefault
textStyleStringfalseFormat of returned richtextportabletext

getEntryMeta() ​

javascript
const articleMeta = await antikythera.getEntryMeta()

Use getEntryMeta() when you only need fast entry chrome, card, or listing data. It calls GET /api/v1/entries/:id/meta and returns the same top-level { settings, entry } envelope as getEntry(), but omits heavy article fields such as content, annotations, bibliography, related entries, the journal about object, and full Sanity asset documents. Since v2, the metadata response includes the entry's markdown file and formatted pdfPreview alongside the pdf.

Response ​

Details
json
{
	"settings": {
		"externalLinks": [
			{
				"linkTitle": "instagram",
				"linkUrl": "https://www.instagram.com"
			}
		],
		"shortDescription": "Journal of Philosophy of Planetary Computation.",
		"blurb": "Journal of Philosophy of Planetary Computation."
	},
	"entry": {
		"title": "Example Entry",
		"url": "example-entry",
		"id": "example-entry",
		"blurb": "A short article description.",
		"doi": "123456789",
		"doiUrl": "https://doi.org/123456789",
		"releaseDate": "2024-09-04",
		"apiExpandable": true,
		"annotationVisibility": true,
		"bgColor": "#266fff",
		"textColor": "#ffffff",
		"theme": 1,
		"featuredImage": {
			"url": "https://cdn.sanity.io/images/y9ih15q4/production/example.jpg",
			"alt": "Example image",
			"caption": null
		},
		"featuredImageSquare": {
			"url": "https://cdn.sanity.io/images/y9ih15q4/production/example-square.jpg",
			"alt": "Example square image"
		},
		"authors": [
			{
				"title": "Firstname Lastname",
				"externalLink": null
			}
		],
		"designers": [],
		"pdf": {
			"url": "https://cdn.sanity.io/files/y9ih15q4/production/example-entry.pdf",
			"pdfMimeType": "application/pdf",
			"pdfFilename": "example-entry.pdf"
		},
		"markdown": {
			"url": "https://cdn.sanity.io/files/y9ih15q4/production/example-entry.txt",
			"markdownMimeType": "text/plain",
			"markdownFilename": "example-entry.txt"
		},
		"pdfPreview": {
			"altText": null,
			"crop": null,
			"hotspot": null,
			"asset": {
				"_id": "image-abc123-1200x1553-png",
				"url": "https://cdn.sanity.io/images/y9ih15q4/production/example-preview.png",
				"metadata": {
					"dimensions": {
						"aspectRatio": 0.7727
					}
				}
			},
			"override": false,
			"downloadLabel": null,
			"downloadUrl": null,
			"url": "https://cdn.sanity.io/images/y9ih15q4/production/example-preview.png?w=520&auto=format"
		},
		"annotationsCount": 2
	}
}

Advanced ​

Parameters

getEntryMeta() supports the same textStyle parameter as getEntry() for rich text fields included in the metadata response:

javascript
const articleMetaWithHtml = await antikythera.getEntryMeta({ textStyle: 'html' })
NameTypeRequiredUsageDefault
textStyleStringfalseFormat of returned richtextportabletext

getAnnotations() ​

javascript
const annotations = await antikythera.getAnnotations()

Side Note

The Antikythera API will only return annotations that have been added to a specific entry from within the Antikythera CMS.

Response ​

Details
json
[
	{
		"content": "is a systematic study of general and fundamental questions concerning topics like",
		"scanText": {
			"enableScanText": true,
			"scanSegments": [
				{
					"scanKeyword": "philosophy",
					"scanPhrase": "Sciences are born when philosophy learns to ask the right questions"
				},
				{
					"scanKeyword": "Philosophie",
					"scanPhrase": "Wissenschaften entstehen, wenn die Philosophie lernt, die richtigen Fragen zu stellen"
				}
			]
		},
		"url": "philosophy",
		"annotationType": "commentary",
		"externalLink": null,
		"doi": null,
		"title": "philosophy",
		"id": "philosophy",
		"featuredImage": {
			"url": "https://cdn.sanity.io/images/y9ih15q4/production/cc9a720edc9fec8bde756dbb6fc655195528a9bf-520x693.jpg",
			"alt": "penseur",
			"caption": ""
		}
	},
	{
		"url": "cosmology",
		"scanText": {
			"enableScanText": true,
			"scanSegments": [
				{
					"scanKeyword": "cosmology",
					"scanPhrase": "This is the planetary in planetary computation: a cosmology in every sense of the word"
				},
				{
					"scanKeyword": "Kosmologie",
					"scanPhrase": "Dies ist das Planetarische in der planetarischen Berechnung: eine Kosmologie im wahrsten Sinne des Wortes"
				}
			]
		},
		"annotationType": "commentary",
		"doi": null,
		"content": "Ancient Greek",
		"featuredImage": {
			"url": "https://cdn.sanity.io/images/y9ih15q4/production/3d5387885f63443a8bb6e31a702eb76fba04dbbd-620x541.jpg",
			"alt": null,
			"caption": ""
		},
		"externalLink": null,
		"title": "cosmology",
		"id": "cosmology"
	}
]

Advanced ​

Parameters

By default, getAnnotations() returns Portable Text for any richtext content. The getAnnotations() method exposes an optional parameter to return a String or raw HTML instead:

javascript
const annotationsWithStrings = await antikythera.getAnnotations({ textStyle: 'string' })

const annotationsWithHtml = await antikythera.getAnnotations({ textStyle: 'html' })
NameTypeRequiredUsageDefault
textStyleStringfalseFormat of returned richtextportabletext

getAnnotation() ​

Fetch one annotation by its Sanity slug or document reference. The menu uses this on demand when a rich-text Annotation Link points to an annotation that is not attached to the current entry.

javascript
const annotation = await antikythera.getAnnotation('annotation-slug')

Portable Text annotation marks retain Sanity's standard _ref. The singular endpoint accepts that document reference and returns the annotation's public slug as id. This lets the menu replace the visible source card without changing the legacy collection or entry projections. Each fetched target retains its own references, so cyclic and recursively linked annotations do not require a fixed projection depth.

Annotation content can contain classifier blocks, inline secondary text, dividers, regular links, and annotation references. With textStyle: 'html', classifier and legacy secondary blocks render as muted paragraphs, the secondary decorator keeps its inline opacity, dividers render as <hr>, and annotation references render as local hash links.

Advanced ​

javascript
const annotationAsString = await antikythera.getAnnotation('annotation-slug', {
	textStyle: 'string'
})
NameTypeRequiredUsageDefault
textStyleStringfalseFormat of returned richtextportabletext

getSettings() ​

javascript
const settings = await antikythera.getSettings()

The settings route includes the journal fields plus the same top-level fonts and fontFaceCss returned by getEntry(). Both client methods install non-empty fontFaceCss into one #antikythera-custom-fonts style element in the host document; a later response replaces that element instead of appending duplicate rules.

Response ​

Details
json
{
	"externalLinks": [
		{
			"linkTitle": "instagram",
			"linkUrl": "https://www.instagram.com"
		}
	],
	"shortDescription": [
		{
			"markDefs": [],
			"children": [
				{
					"_type": "span",
					"marks": [],
					"text": "A Journal of Philosophy of Planetary Computation.\n\n",
					"_key": "45fb4d058d160"
				}
			],
			"_type": "block",
			"style": "normal",
			"_key": "2d544012f8bd"
		},
		{
			"markDefs": [],
			"children": [
				{
					"marks": [],
					"text": "Coming Spring 2025",
					"_key": "d079d3f606f00",
					"_type": "span"
				}
			],
			"_type": "block",
			"style": "normal",
			"_key": "5ee97e6097ec"
		}
	],
	"blurb": "A Journal of Philosophy of Planetary Computation.",
	"fonts": [
		{
			"title": "Example Sans",
			"slug": "example-sans",
			"cssFamily": "example-sans",
			"files": [
				{
					"url": "https://cdn.sanity.io/files/y9ih15q4/production/example-sans.woff2",
					"weight": 400,
					"style": "normal",
					"filename": "example-sans.woff2",
					"format": "woff2"
				}
			]
		}
	],
	"fontFaceCss": "@font-face{font-family:'example-sans';font-style:normal;font-weight:400;font-display:swap;src:url('https://cdn.sanity.io/files/y9ih15q4/production/example-sans.woff2') format('woff2');}"
}

Advanced ​

Parameters

By default, getSettings() returns Portable Text for any richtext content. The getSettings() method exposes an optional parameter to return a String or raw HTML instead:

javascript
const settingsWithStrings = await antikythera.getSettings({ textStyle: 'string' })

const settingsWithHtml = await antikythera.getSettings({ textStyle: 'html' })
NameTypeRequiredUsageDefault
textStyleStringfalseFormat of returned richtextportabletext

HTTP caching and errors ​

Production API reads use public cache headers. Metadata and settings responses are cached more aggressively than full entry and annotation responses; non-production environments and error responses use Cache-Control: no-store.

Missing entries on strict metadata routes return HTTP 404 with a stable JSON error envelope:

json
{
	"error": {
		"message": "Entry \"example-entry\" was not found",
		"statusCode": 404
	}
}

Options ​

When creating the Antikythera class, entry is required for entry-specific queries and components. getSettings() can still be used without it.

NameTypeRequiredUsageDefault
entryStringtrue*Entry slug for entry-specific queries and components.''
menuNameStringfalseCustom element name registered for the menu.antikythera-menu
footerNameStringfalseCustom element name registered for the footer.antikythera-footer
annotationClassStringfalseSelector class used to find inline annotations..annotation
customCssBooleanfalseWhen true, do not inject the package's default annotation-highlight CSS.false
manualBooleanfalseSkip automatic initialization; call antikythera.init() yourself.false
detectAnnotationsOnInitBooleanfalseScan and observe annotations during initialization.true
environmentStringfalseSanity dataset passed as the env query parameter.production
apiUrlStringfalseRuntime API host override; must be an http or https origin without credentials.baked-in host/fallback
analyticsBoolean / ObjectfalseDisable analytics or configure its mode, replay, exceptions, autocapture, and PostHog overrides.true

entry is optional only for settings-only use.

Advanced queries ​

The Antikythera npm package is built on top of a Sanity CMS backend. If its capabilities are insufficient, you can use the Sanity API directly to perform more complex queries. Please consult the Sanity Client Documentation to learn how to use the Sanity API. Once you have the projectId, you can query the database with the Sanity client like this:

INFO

Please contact Antikythera to get the sanity projectId.

javascript
import { createClient } from '@sanity/client'

const client = createClient({
	projectId: '${antikythera-project-id}',
	dataset: 'production',
	apiVersion: '2023-06-01',
	useCdn: false
})

const query = `{
	'entry': *[_type == "entry" && url.current == "example-entry"][0]{
		title,
		introduction,
		content[]{
			...,
			"markDefs": markDefs[] {
				...,
				"slug": @->url.current
			}
		}
	}
}`

const fetchEntry = async () => {
	try {
		const result = await client.fetch(query)
		console.log('Entry data:', result.entry)
		return result.entry
	} catch (error) {
		console.error('Error fetching entry:', error)
	}
}

const entry = await fetchEntry()