Pack format
A pack is one JSON file. mentria plays it as cards you read, answer and come back to. Anything that writes JSON can make one, including an AI assistant working from a book or your notes.
The file
Save it as something.mentria.json and import it on the Learn page, drop it there, or open https://mentria.ai/learn/?pack=<url> where the URL points at a file served over HTTPS. Packs stay on the device that imported them. Nothing is uploaded.
{
"id": "attention-basics",
"version": 1,
"title": "Attention in five cards",
"subtitle": "How a token decides what to look at.",
"cover": "https://example.com/cover.webp",
"author": { "name": "you" },
"language": "en",
"tags": ["ai"],
"minutes": 5,
"sections": [
{ "id": "s1", "title": "The idea", "cards": ["c1", "c2"] },
{ "id": "s2", "title": "Check yourself", "cards": ["c3", "c4"] }
],
"cards": [
{ "id": "c1", "type": "slide", "caption": "Every word gets to look at every other word.", "body": "One move, called **attention**, is under almost everything a model does." },
{ "id": "c2", "type": "image", "image": "https://example.com/parts.webp", "caption": "Tap the three parts.",
"hotspots": [ { "x": 8, "y": 20, "w": 26, "h": 22, "label": "Query", "body": "What this token is looking for." } ] },
{ "id": "c3", "type": "mcq", "question": "What does the **query** represent?",
"choices": [
{ "text": "What the current word is looking for", "correct": true, "why": "The query is the question a word asks of the others." },
{ "text": "The final answer the model prints", "why": "That comes much later." }
] },
{ "id": "c4", "type": "checkpoint", "summary": "Query asks, key answers, value carries." }
]
}
Top level
| field | required | notes |
|---|---|---|
id |
yes | letters, digits, dots, dashes. Stable across versions. Importing the same id again replaces the pack. |
version |
no | integer, default 1. Bump it when you edit. |
title |
yes | text |
subtitle, cover, author, language, tags, minutes |
no | cover is a URL or data URI. minutes is an estimate; it is computed from the card count when missing. |
modes |
no | subset of read, quiz, review, budget. Default is all four. |
sections |
no | ordered groups of card ids. Every card should be in exactly one section. Without sections the pack is one section in card order. |
cards |
yes | at least one card, at most 2000 |
Any text field can be a string or a map of language codes to strings, for example { "en": "Hello", "fr": "Bonjour" }. Text fields accept Markdown.
Cards
Every card has id (unique in the pack) and type. Every card may also have title, image, after (ids of cards that should come first) and guess (see below).
| type | fields | what the learner does |
|---|---|---|
slide |
caption, body, image |
reads |
image |
image, caption, hotspots[] of {x, y, w, h, label, body} in percent |
taps the spots; tapping all of them counts as done |
mcq |
question, choices[] of {text, correct, why}, multi, shuffle |
picks one, or every correct one when multi is true. why shows after answering. |
cloze |
text with `` markers, answers[] (one per blank; an array lists accepted alternatives), chips[] extra distractors |
fills the blanks with chips when chips is given, otherwise by typing |
order |
prompt, items[] in the correct order |
puts the shuffled items back in order |
match |
prompt, pairs[] of [left, right] |
pairs each left item with its right item |
canvas |
html (one self-contained HTML document, up to 512 KB) |
uses your interactive; see below |
ask |
prompt, model_answer, rubric |
writes an answer. The on-device model grades it when one is already set up, otherwise the learner compares and grades themselves. |
checkpoint |
summary |
reads a recap; the card shows the section's score |
Guess before reveal
Add guess to any card to ask for a prediction first:
"guess": { "prompt": "How many tokens can the window hold?", "kind": "number", "answer": 16384, "unit": "tokens" }
kind is number, range (add min, max, optional step) or choice (add choices[] and answer as the index of the right one). Number guesses within 10% count as right.
Canvas cards
The HTML runs in a sandboxed frame with no access to the site or its storage. It gets a small window.mentria object:
window.mentria.theme // { accent, bg, fg, muted, fontMono, fontBody }
window.mentria.lang // the page language, e.g. "en"
window.mentria.done(right) // record the card as answered; pass false for a wrong answer
window.mentria.next() // move to the next card
window.mentria.notify(text) // show a short line under the frame
The frame inherits the site's content security policy, so external stylesheets, scripts and fonts never load; a Google Fonts link fails silently. The site's own faces are already available by name inside the frame: Inter for reading and JetBrains Mono for code and labels, and body starts with the theme's background, colour and body font. Images may be data URIs or hosted on cdn.mentria.ai. The page is dark only and the frame is set to color-scheme: dark; prefers-color-scheme inside the frame follows the visitor's operating system, not the site, so do not branch on it. Read colours from window.mentria.theme or write the dark values directly. Keep everything else inline.
Modes
The learner chooses how to play, and the same pack serves all four modes. Read shows every answer. Quiz grades. Review replays only the cards they missed. Today mixes due reviews with new cards up to a daily budget. Cards answered wrong come back after 1, 3, 7, 16 and 35 days.
Courses
A syllabus is a course: one JSON file that carries several packs in order. Import it once and the library shows the packs grouped under the course name; the home stream serves the first unfinished pack, then the next, so the order you wrote is the order they learn in.
{
"kind": "course",
"id": "signals-101",
"version": 1,
"title": "Signals and systems",
"subtitle": "Twelve weeks, one pack per lecture.",
"cover": "https://example.com/cover.webp",
"packs": [
{ "id": "signals-101-w01", "title": "Week 1: what a signal is", "cards": [ ... ] },
{ "id": "signals-101-w02", "title": "Week 2: sampling", "cards": [ ... ] },
"https://example.com/signals-101-w03.mentria.json"
]
}
Each entry in packs is a full pack or an HTTPS URL to one. A course may hold up to 200 packs. Re-importing a course with the same id updates its packs in place and keeps your progress on cards whose ids did not change.
To assemble a course from a folder of pack files:
node scripts/pack-course.mjs ./my-course --id signals-101 --title "Signals and systems"
It validates every pack, orders them by filename (or by an order field inside each pack), and writes signals-101.mentria-course.json.
Checking a pack
The repository ships a validator:
node scripts/pack-check.mjs my-pack.mentria.json
It prints the outline and every problem it finds, for a single pack or a whole course. The same rules run in the browser on import.
Limits
A pack may be at most 25 MB as JSON. Images should be small WebP files or hosted URLs. Card ids and pack ids must match [a-z0-9][a-z0-9._-]*.
Sharing
From the Learn page a pack can be shared as a file, and in Comms it can be sent straight to a contact, who imports it with one tap.