Status: Editor’s Draft, 2026-10-17. Not stable. Anything may change before 1.0 is frozen. Editors: Gendo Kumoi (Nagaoka University of Technology). Reference implementation: motion-academic-slides (producer, player, editor). License of this document: CC BY 4.0. Schemas, fixtures and tools are MIT (LICENSE). Repository: https://github.com/egatari/egatari-spec · Site: https://egatari.org/
Egatari (絵語り, “narrating with pictures”) is a file format for narrated, animated lessons. One Egatari file is a single HTML document that
The name is written Egatari (or 絵語り). The file extension is .ega.html.
The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, NOT RECOMMENDED, MAY, and OPTIONAL are to be interpreted as described in BCP 14 [RFC 2119] [RFC 8174] when, and only when, they appear in all capitals.
JSON structures are normatively defined by the JSON Schemas in schema/. Where prose and schema disagree, the schema wins and the disagreement is a bug in this document.
“Step key” means "<slideKey>:<stepKey>", where stepKey is a non-negative integer in decimal or the literal concl. Slide keys may themselves contain :; a step key is split at its last :.
A lesson is a sequence of slides. Each slide has steps 0..n and optionally a final concl step. The position is the triple (i, k, t): slide index, step, and seconds since the step began.
| Mode | Who drives | Time | Typical use |
|---|---|---|---|
talk (default for a bare file) |
the presenter, step by step | real time of each step | live presentation, class |
learn |
the learner: start, pause, previous/next, jump, playback rate | advances along the timeline (§5.5) with narration audio | self-study, LMS (default in LMS bindings and hosted players) |
video |
nobody (no controls) | advances along the timeline | recording and video export |
Preview of later steps is an option of every interactive mode, not a mode: steps not yet reached are either hidden (default) or shown faintly. The current step renders and animates as usual; only content of later steps is shown faintly, and components keep rendering their current step. ?mode=lecture is a legacy alias for talk with preview on.
In learn mode, a player MUST offer playback rates of at least 0.75, 1, 1.25, 1.5 and 2, keeping the pitch of speech; the timeline clock advances at the same rate, so the screen stays a function of (i, k, t). Presenter-only tools (presenter view, laser) SHOULD be hidden.
When embedded by a host (§9.4, §10), the host MAY hide the player’s own controls (controls=0) and provide equivalent ones itself (chapter list, playback rate, pause), and MAY restrict forward navigation as a course policy. The requirement to offer controls is then met by the host.
Each step MAY have narration: an ordered list of turns, each with a role (expert or novice; single-speaker lessons use expert only) and text. Text MAY contain boundaries — cue (a synchronization point for an animated component) and mark (an attention mark on a target element) — and readings (display text with a separate spoken form, e.g. φ read as ファイ). The authoring syntax is defined in §8.
An Egatari file is an HTML document (text/html; charset=utf-8). The RECOMMENDED file name extension is .ega.html. A file has two layers:
| Layer | Purpose | Required for |
|---|---|---|
| Runtime layer | compiled slides, runtime script, styles, fonts, and the runtime JSON blocks (§4) | playing |
| Archive layer | the index (§3) and blob blocks: authoring source and media | editing |
A file with only the runtime layer is a play-only Egatari file. A file with both is a portable Egatari file.
All JSON blocks are <script> elements with a non-executable type and a unique id. Before embedding (and before hashing), every U+003C < in serialized JSON MUST be written as the six ASCII characters \u003c. Serialized JSON uses LF line endings only. (A non-executable type does not stop the HTML parser from ending the element at a literal </script.)
A conforming document MUST also contain, as ordinary DOM outside any <script>, for every step of every slide: the display text of its narration. It SHOULD contain, for every slide, either a static image of the final state or text equivalent to it. Producers MAY also include a final-state image per step, so that hosts can show stills without running a browser. This layer serves archiving, accessibility, search, and recovery when no script runs.
Structure: one <section id="egatari-readable"> containing, per slide, <article data-slide-key="…"> with the slide title, and per step <div data-step-key="…"> with one <p data-role="expert|novice"> per turn (display text). Images, if any, are <img alt="…"> inside the article or step. The runtime hides this section when it starts; without script it stays visible. Within an article, step divs are in step order (numbers ascending, concl last). Narration addressed to a slide key that matches no slide has no article: such steps are omitted from the readable layer and reported. Tier A edits update the affected div elements (§7.2).
<script type="application/vnd.egatari+json" id="egatari-index" data-sha256="…">{…}</script>
data-sha256 is the lowercase hex SHA-256 of the exact UTF-8 text content of the element. Readers MUST verify it before use.egatari-index, egatari-signatures, or any egatari-b-*) occurring more than once anywhere in the document — including inside <template> or foreign (SVG/MathML) content — makes the file invalid.egatari: references while parsing; otherwise players resolve them after parsing completes.schema/egatari-index.schema.json. Its main members:| Member | Req. | Meaning |
|---|---|---|
format |
MUST | the string "egatari" |
version |
MUST | {major, minor} of this specification the index follows (1.0 → {"major":1,"minor":0}) |
profile |
MUST | "core" or "extended" (§6) |
requires |
MUST | feature identifiers a reader must understand to edit (§3.5). May be empty |
contracts |
MUST | {source, runtime} integer versions of the authoring syntax and of the runtime behavior (§7) |
generator |
MUST | {name, version} of the producing software |
docId |
MUST | a UUID identifying the lesson across edits; editors MUST preserve it |
entry |
MUST | archive path of the slide source (normally deck.html) |
sourceSha256 |
MUST | hash of the source set the runtime layer was built from (§3.4) |
files |
MUST | archive path → blob id |
blobs |
MUST | blob id → {mime, bytes, sha256, chunks} |
audio |
MAY | step key → audio record (§5.2) |
components |
MAY | components used (§6) |
themes |
MAY | themes used, same shape as components (§6) |
metadata |
MUST | descriptive metadata (§3.6) |
excluded |
MAY | informative list of categories the producer deliberately left out |
<script type="application/octet-stream" id="egatari-b-s0-0">PCFkb2N0eXBlIGh0bWw+…</script>
blobs[id].chunks.bytes and its SHA-256 MUST equal sha256; otherwise the blob is corrupt.^[A-Za-z0-9_]{1,32}$. Chunk ids are egatari-b-<blobId>-<n> with <n> the zero-based chunk number in canonical decimal (at most 9999); chunks[i] MUST be egatari-b-<blobId>-<i>. Duplicate ids make the file invalid.gzip-v1) may add compression; it will be listed in requires.Keys of files are archive paths: /-separated, relative, UTF-8, NFC. A reader MUST reject the whole file if any path
/, contains a drive letter (C:), a UNC prefix, a backslash, a NUL or other control character;. or .., ends in a dot or space, or is a Windows reserved device name (CON, PRN, AUX, NUL, COM1–COM9, LPT1–LPT9, with or without extension);__proto__, constructor or prototype in any segment;< > : " | ? * or a C0/C1 control character (U+0000–U+001F, U+007F–U+009F), ends with /, or is not in Unicode Normalization Form C;COM¹–COM³, LPT¹–LPT³);/… (a path cannot be both a file and a directory).Case-insensitive comparison uses the key NFC(lower(upper(NFC(path)))), where upper and lower are the locale-independent Unicode case mappings (as ECMAScript toUpperCase/toLowerCase). A path is at most 1024 bytes and 10 segments. Readers MUST build every key-indexed structure from untrusted input with maps that have no inherited keys.
Paths under audio/ are reserved for narration audio; their blobs MUST have an audio media type from §5.1. audio/manifest.json MUST NOT appear in files: it is derived from index.audio when an archive is unpacked. Producers SHOULD include a root file egatari.json ({"docId", "metadata"?}) so that an unpacked folder keeps its docId; when present, its docId MUST equal the index’s. Editors MAY add it to a document that lacks it (this changes sourceSha256, which the edit updates anyway). An archive MUST include the slide source (entry), narration.md if the lesson has narration, and the complete dependency closure needed to rebuild the runtime layer (images, data, styles, deck-local components and themes, macros, timeline.json, a voice configuration without private paths). Registry components (§6) are excluded from the closure; they are resolved by exact name, version and hash. Metadata embedded in images (EXIF, XMP) SHOULD be stripped when an image is first added to a lesson rather than at export, so that rebuilds stay byte-identical. It MUST NOT include: absolute or machine-specific paths, credentials, editor review notes, caches, backups, unreferenced audio, private reference-voice recordings, or personal identifiers not deliberately entered as content.
sourceSha256 is computed as SHA-256 over the UTF-8 bytes of the lines "<path>\t<sha256>\n", one per entry of files excluding paths under audio/, sorted by path in code-point order. Producers MUST also write the same value into the runtime layer’s deck-meta.sourceSha256 (§4). Readers MUST recompute it from files and the blob hashes; a mismatch with the index value is treated like a mismatch with deck-meta. An editor that finds any two of these values different MUST warn that the compiled lesson and its source disagree and MUST NOT perform a tier A edit (§7.2).
requires lists feature identifiers (^[a-z0-9-]+$). Version 1.0 defines sources-v1 (archive layer present) and audio-v1 (§5.2 records). A reader MUST refuse to edit or re-export a file whose requires contains an identifier it does not implement; it MAY still play the file and extract its source (§9.3) when the index and blobs validate. Source extraction is not re-export. Optional information that a reader may ignore MUST NOT be listed in requires.
metadata MUST contain title and lang (BCP 47). It MAY contain license (SPDX identifier or URL), copyright (e.g. © 2026 Gendo Kumoi), attribution (the credit line the rights holder asks reusers to show), creators (display names the author chose to publish), description, accessibility (schema.org terms such as accessMode, accessibilityFeature, accessibilityHazard), curriculum (array of curriculum codes, e.g. Japanese 学習指導要領 codes from the MEXT education data standard), completion (§9.2), and the members of §3.6.1. Producers MUST NOT add timestamps, device identifiers, account e-mail addresses, or file-system locations.
These OPTIONAL members support lessons made for one learner, by learners, or by software agents.
derivedFrom: {docId, sourceSha256, anchor?} — the lesson this one was derived from (e.g. a version adapted for one learner, or a short branch answering a question, where anchor is the step key it branches from). An editor that changes the original can then find derived lessons and offer to carry a fix over, step by step, using slide ids.authorship: {<stepKey or slideKey>: {by, reviewed}} with by one of human, ai, mixed and reviewed a boolean (a person responsible for the lesson has checked it). Keys without an entry are unknown. Names of reviewers are not recorded (§3.6).audience: "public" (default) or "individual". An individual lesson is made for a specific learner or group and SHOULD NOT be redistributed; hosts MUST NOT show it to other learners. It still MUST NOT contain learner records (§9.1A): it may be tailored to a learner, but grades, answers or personal data are not part of it.A play-only derivative is made from a portable file by removing the index and every blob whose chunks are not referenced by the runtime layer, keeping the chunks of audio referenced as egatari:<blobId>. In deck-meta, portable becomes false and sourceSha256 is removed; docId is kept. Players resolve egatari:<blobId> by decoding the chunks egatari-b-<blobId>-0, -1, … in order until the next one is absent.
The archive layer — including extensions — is readable by anyone who holds a portable file. A host that must keep information from learners (e.g. quiz answers in an extension) MUST serve learners a play-only derivative and keep the portable original for authorized editors.
The runtime layer contains the compiled slides (<section class="slide" data-key="…"> elements), the runtime, and these JSON blocks (type="application/json"):
| id | Content |
|---|---|
deck-meta |
{format:"egatari", version, contracts, generator, docId, title, lang, portable, sourceSha256?, completion?, tracking, license?, copyright?, attribution?} — schema schema/deck-meta.schema.json. tracking lists the tracking adapters the runtime implements (e.g. ["scorm12","postmessage","local"]), so that packagers and hosts need not read the archive |
deck-narration |
{slideKey: {stepKey: [{role, text}]}} — display text |
deck-marks |
{stepKey: [{type:"cue"} \| {type:"mark", id, effect, hold} \| {type:"clear"}]} — boundaries, only for steps with marks |
deck-audio |
{stepKey: {src, dur, cues}} — src is a data: URI or egatari:<blobId>; dur seconds; cues the time of every boundary in order (§5.3); sync (auto, manual, none; default auto); optional mime (the audio media type, so that players of play-only derivatives need not sniff) — schema schema/deck-audio.schema.json |
deck-timing |
{stepKey: {dur?, hold?, lead?, pad_after?}} — explicit overrides |
Every slide MUST carry a data-key: the slide’s id attribute if present, otherwise its normalized heading text (legacy). Producers SHOULD give every slide an explicit, stable id (§8.1), so that editing a title does not break narration and audio associations.
A portable file SHOULD reference audio as egatari:<blobId> so that audio bytes are stored once, in the archive layer. A player resolves egatari:<blobId> by decoding that blob.
| Format | Producer | Player |
|---|---|---|
MP3 (audio/mpeg), mono, 44.1 kHz |
RECOMMENDED for new audio | MUST play |
AAC-LC in MP4 (audio/mp4) |
MAY (existing audio SHOULD be kept as is) | SHOULD play |
WAV PCM 16-bit (audio/wav) |
MAY, emergency only | MUST play |
Producers SHOULD normalize speech to −20 LUFS integrated, −1.5 dBTP, and trim silence at the ends (−45 dB, keeping 0.05 s), and SHOULD insert 0.35 s of silence between turns, but not at boundaries inside a turn nor at the end of the step. Encoder delay (priming) MUST be compensated so that time 0 of dur and cues is the first audible sample a player presents.
index.audio[stepKey] describes the step’s audio (schema: schema/egatari-audio.schema.json):
| Member | Req. | Meaning |
|---|---|---|
file |
MUST | archive path under audio/ |
dur, cues |
MUST | as in deck-audio |
kind |
MUST | tts, recorded, or imported |
inputHash |
MUST | §5.4; null when unknown (e.g. audio imported from an older build), which players and editors treat as stale |
recipe |
MUST for tts |
{engine, model, voice, seed, dictionary, processing}; voice is a public identifier, never a path; dictionary an identifier of the effective reading dictionary and rules (SHA-256 RECOMMENDED); processing a free-form profile string, RECOMMENDED as key=value pairs separated by ; |
recipeDigest |
MAY | the producer’s content-address of input + recipe (e.g. the reference implementation’s 12-hex segDigest) |
alternatives |
MAY | [{file, mime, startOffset}] other encodings of the same take |
Editors MUST NOT replace recorded audio unless the user explicitly selects that step.
The boundaries of a step, listed in order across its turns, correspond one-to-one with cues. Players align component cues(k) with the cue boundaries only, and start marks at cue + lead. When counts do not match, or sync is none, players MUST fall back to unsynchronized deterministic timing (no component stretching; marks every 0.9 s after the step’s motion ends). Producers of recorded audio MUST NOT reuse cue times from a previous take; until the user places them, the record (and its deck-audio entry) MUST have "sync":"none" and cues: [].
Three hashes serve three purposes and MUST NOT be substituted for one another:
inputHash — lowercase hex SHA-256 of the UTF-8 bytes of the canonical string JSON.stringify(turns.map(t => [t.role, t.ttsParts, t.bounds.map(token)])) (ECMAScript JSON.stringify, no whitespace), where role is "expert" or "novice", ttsParts are the spoken text pieces between boundaries (readings substituted, trimmed, empty pieces kept), and token is "cue", "mark:clear", or "mark:<id>|<effect>" followed by "|hold=<n>" when a hold is given (<n> formatted by ECMAScript Number.prototype.toString). The slide key is not part of the hash. Test vectors: fixtures/input-hash/. Audio is stale when the current narration’s inputHash differs from the record’s. Editors MUST show stale steps and MUST NOT silently delete or regenerate their audio. When a step’s narration is removed altogether, its audio is removed with it, with a warning.recipe / recipeDigest — records how the audio was generated. Identical recipes do not imply identical bytes: GPU-based synthesis is not bit-reproducible across hardware. Readers MUST NOT use audio bytes to decide whether a step needs regeneration.sha256 of the blob — integrity only.The length of step (i, k) is
dur = override.dur ?? max(anim, audio ? lead + audio.dur + pad_after : max(hold, marksEnd + pad_after))
with defaults lead = 0.25, pad_after = 0.6, hold = 1.2 seconds; anim is the step’s motion length including the slide transition (0.35 s) or morph (0.9 s) at step 0. video mode MUST use this function; so MUST any video export.
Animated figures are custom elements (Web Components) following the component contract (§8.4). Each entry of index.components is {tag, name, version, sha256, source} where source is registry or embedded. A component’s sha256 covers its code file (index.js) only. Each entry of index.themes is {name, version, sha256, source}; a theme’s sha256 is SHA-256 over the lines "<relative path>\t<sha256>\n" of its files, sorted by path. version is the registry release for registry entries; embedded entries without a version use "local".
| Profile | Components | Editor behavior |
|---|---|---|
| Core | Only registry components: identified by name, version and the SHA-256 of their code in a public component registry. The archive contains no component code. |
The editor supplies its own trusted copy and verifies the hash. A component it does not have is shown as its final-state image or text equivalent. |
| Extended | embedded components: code is in the archive (pinned at export). |
The editor MUST run component code only in a sandbox with an opaque origin and a policy that forbids network access, and MUST tell the user that the lesson contains its own interactive parts before any of that code runs. “Forbids network access” means at least connect-src 'none', with every other resource type limited to data: and blob:. |
The runtime layer of either profile contains the component code needed to play offline. An Extended document MAY mix registry and embedded components. A document is Extended if any archived file other than registry components contains executable script (a <script> element without a data type, or a JavaScript file); otherwise it is Core. Readers MUST NOT rely on the declared profile: they MUST determine whether code can run from the validated content — at least: <script> elements of any non-data type; event-handler attributes (on*); javascript:/vbscript: URLs in any URL-valued attribute, including SVG animation values; srcdoc; embedding elements (iframe, object, embed, frame) and meta http-equiv=refresh; JavaScript files; and HTML or SVG documents the source includes or references. Registry components that evaluate expressions written in attributes are a code path even in a Core document (trusted code, untrusted data): such components MUST restrict what those expressions can do (e.g. arithmetic only), and readers SHOULD treat expressions outside that restriction as code. Classification MUST be performed on the expanded source — after every include or preprocessing step, covering every dependency read as text regardless of its file extension — and on the same bytes that are then rendered; a dependency that cannot be read makes the document count as containing code. and treat a document whose content contradicts its declared profile as Extended or reject it. If an editor cannot obtain a trusted copy of a registry component with the recorded hash, it MUST disable tier B and C edits for that document and MAY still perform tier A edits.
version (this specification): minor versions only add optional members; editors MUST preserve unknown members through edits, at every level of the index — including inside metadata, audio records and component entries. A reader MUST refuse to edit a file with an unknown major version, and SHOULD still play it. It MAY extract the source using the rules of the highest major version it implements, if the index validates against them; otherwise it refuses extraction too.contracts.source: the authoring syntax (§8). Editors migrate older source contracts with deterministic migrators, and refuse newer ones.contracts.runtime: the runtime behavior. See §7.2.generator.version is informative.An editor MUST NOT execute a builder or runtime supplied by the document in its own trusted context.
| Tier | Change | Required behavior |
|---|---|---|
| A (default) | narration text, audio, timing overrides (timeline.json / deck-timing; durations themselves are computed, §5.5) only |
Do not rebuild. Only these regions may change: the affected entries of the runtime JSON blocks; the affected entries of the readable layer (§2.1); sourceSha256 (index and deck-meta) and the index hash; archive entries, audio records and blobs; and append-only font supplements — new <style id="egatari-font-supplement-<n>"> elements carrying glyphs for characters not covered by the embedded fonts. When saving under a new docId (§7.3), deck-meta.docId, the index docId, metadata.derivedFrom and egatari.json are also tier A regions. Every byte outside these regions, including existing font data, MUST stay identical. |
| B | slide content (text, math, figures) | Rebuild with the editor’s runtime of the same contracts.runtime, using the archived (pinned) components and themes. Before saving, compare the final state of every slide before and after; show the user every slide that changed besides the edited ones. |
| C | contracts.runtime differs from the editor’s |
Only on explicit request (“upgrade”), with the same comparison as B. Without upgrade, only tier A edits are allowed. |
Re-export MUST copy untouched blobs byte-for-byte and MUST preserve docId (unless saving as a new lesson, §7.3).
Font supplements. <n> in egatari-font-supplement-<n> is canonical decimal, one greater than the largest existing supplement (starting at 0); a supplement follows the last existing font style element. It contains @font-face rules with the same family, weight and style as each embedded face that needs the new characters. Producers SHOULD declare unicode-range on every embedded @font-face; editors MAY add a supplement for any character whose coverage they cannot establish from those ranges.
Egatari cannot prevent anyone from modifying a file (§11). It carries the rights holder’s terms so that conforming software states them and keeps credit intact:
license, copyright and attribution from the metadata into deck-meta, so that play-only derivatives (§3.7) keep them.-ND license, or no license with a copyright notice), the editor MUST warn that modifying or redistributing the lesson may infringe the rights holder’s copyright and MUST require explicit confirmation.docId (e.g. “save as a new lesson”) MUST keep the original copyright, attribution and creators, MUST set derivedFrom (§3.6.1), and MAY add the new editor’s name to creators. An editor that keeps the docId MUST NOT set or change derivedFrom.copyright and attribution without the user’s explicit action.This section will incorporate, normatively, the contract of the reference implementation:
docs/contract.md§1–§5, §7, §8 (position and time, modes, components, narration syntax, timeline, attention marks, morphs). Until then, that document is normative forcontracts.source = 1andcontracts.runtime = 1.
<section class="slide" id="…">. Ids match ^[A-Za-z][A-Za-z0-9_-]{0,63}$ and are unique in the document — including against every other id in it, such as attention-mark targets.s- followed by 6 random base32 characters ([a-z2-7]). They MUST NOT be derived from the heading, so that renaming a heading never changes the id.narration.md, a slide is addressed as ## <heading> {#<id>} (RECOMMENDED), ## <id>, or ## <heading> (legacy, only for slides without id). When an {#id} is present it wins over the heading text.deck.html. A slide’s key is, in order of precedence: its id; a hand-written data-key attribute (legacy); the normalized text of its first heading element; s<n> with n the 1-based position of the slide (legacy). Producers SHOULD warn when a slide is keyed by a legacy rule.narration.md. A heading line is ## followed by the heading text, optionally followed — with or without whitespace before it — by {#<id>} at the end of the line (## {#<id>} alone is allowed). If {#…} is present, its content is the key; if that content is not a valid id (§8.1 pattern), readers MUST report an error and still use the content as the key. Otherwise the key is the normalized heading: HTML tags removed, treated as a space, and all whitespace removed. (A heading consisting of an id normalizes to that id.)deck.html.0, 1, … without leading zeros), at most 999, or concl. Readers MUST NOT treat a non-canonical marker such as [01] or [1000] as a step and SHOULD report it; lines up to the next marker or heading then belong to no step.## <heading> {#<id>}
[0] text…
[2] Expert: text with {cue} and {mark:ID|circle|hold=2} and {φ|ファイ}
Novice: question…
[concl] text…
Extension lines. Inside a step’s block, a line of the form @x-<name>-<field> <value> (after optional indentation; <name> as in §9A, <field> matching ^[a-z][a-z0-9-]{0,31}$, <value> the rest of the line, single-line UTF-8) carries extension data for that step. Extension lines:
ttsParts, boundaries and inputHash (§5.4), so editing them never makes audio stale;extensions.<name>[<stepKey>].<field>; repeated lines with the same name and field become an array in their order;[k] marker line, others stay after the turn they followed.{display|reading} stores the reading in kana (or the lesson language’s ordinary spelling). Producers MAY export readings as SSML <sub alias>, W3C PLS lexicon entries, or HTML <ruby>.
steps, props, mount(ctx), duration(k), cues(k), render(k, t, ctx); render gives the same output for the same (k, t); colors only through theme CSS variables.
A conforming player MUST: render any (i, k, t) deterministically; implement the talk, learn and video modes and the preview option (§1.2); play the formats in §5.1; start audio only after a user gesture (a visible start/resume control); pause when the page is hidden; honor prefers-reduced-motion by rendering final states; and work from file:// with no network access.
Notes, ink, bookmarks and other data a learner creates MUST NOT be written into the Egatari file. Players MAY keep them in local storage keyed by docId, and SHOULD let the learner export them (e.g. the final state of each slide with the learner’s notes, as PDF or Markdown).
A player SHOULD expose progress through a tracking adapter, detected in this order: SCORM 1.2 API (window.API in a parent frame), then postMessage to the parent, then local storage. Events: started, step ({slideKey, stepKey, index, total}), completed, suspend ({location, data}). The SCORM 1.2 binding is defined in bindings/scorm12.md. A player lists the adapters it implements in deck-meta.tracking; completion rules are read from deck-meta.completion so that play-only files can be tracked. Completion defaults to “every step visited”; metadata.completion MAY set {"visitedFraction": number} or {"reachedEnd": true}.
A player SHOULD offer to save the archive layer as a ZIP of files plus audio/manifest.json, so the source remains recoverable without any editor.
A host page (a learning platform, an LMS plugin, a video tool) MAY embed an Egatari file in an <iframe> and drive it. A player that supports embedding MUST, when loaded with the query parameter embed=1, accept messages from window.parent via postMessage (objects with a string member egatari) and ignore messages from any other source. Query parameters mode, controls=0 and preview=1 set the initial state. Every command MAY carry an id; the player echoes it in the reply or acknowledgement. Times are seconds as floating-point numbers.
Commands (host → player):
| Message | Effect / reply |
|---|---|
{egatari:"seek", id?, slide, step, t} |
go to (slide, step, t); slide is a slide key or index, step a step key, t seconds or null for ∞. Reply seeked |
{egatari:"play", id?} / {egatari:"pause", id?} |
start / pause timeline playback with audio. If the browser refuses to start audio, reply playBlocked |
{egatari:"rate", id?, value} |
playback rate (§1.2) |
{egatari:"mode", id?, mode} / {egatari:"preview", id?, on} / {egatari:"controls", id?, visible} |
display mode, preview of later steps, the player’s own controls |
{egatari:"snapshot", id, slide, step, t, maxWidth?} |
reply {egatari:"snapshot", id, png} with a data:image/png of that position (t null = ∞), without changing the visible position |
Events (player → host; target origin *; payloads contain no private data):
| Message | When |
|---|---|
{egatari:"ready", version, docId, sourceSha256?, timeline:[{slideIndex, slideKey, stepKey, slideTitle, dur}]} |
after loading |
{egatari:"seeked", id?, slideKey, stepKey, t} |
after a seek has been applied |
{egatari:"position", slideKey, stepKey, t, cause} |
on every step change (cause: play, seek, control), and at most 4 times per second during playback |
{egatari:"stepEnded", slideKey, stepKey} |
when a step has played to its end during playback (not when skipped) |
{egatari:"state", playing, rate} |
whenever playing state or rate changes, including by the player’s own controls |
{egatari:"playBlocked", id?} |
audio could not start for lack of a user gesture |
{egatari:"turn", slideKey, stepKey, index} |
OPTIONAL: when narration turn index starts |
{egatari:"ended"} |
after the last step of the timeline |
{egatari:"error", id?, code, message} |
a command failed or the file cannot be played |
The first start of audio SHOULD happen through the player’s own start control, because browsers (notably Safari on iPad) may not pass a user gesture in the host page into the frame; hosts SHOULD allow autoplay on the frame and MUST handle playBlocked. Since a sandboxed frame has the origin null, hosts MUST identify the player by event.source === iframe.contentWindow. The same messages serve the postMessage tracking adapter (§9.2). Embedding commands are unprivileged: they can only do what a viewer could do with the controls. Narration text is not sent at run time; hosts read deck-narration from the file.
Platforms may need information that Egatari does not define (e.g. questions to ask the learner at a step, grouping of steps into chapters). Such information is carried as extensions without changing Egatari itself.
extensions is an object whose keys are extension names (^[a-z][a-z0-9]{0,31}$ — no hyphen, so that x-<name>-<field> is unambiguous) and whose values are objects with at least v (integer version of that extension; producers write 1 unless the extension’s definition says otherwise). Data addressed to a step uses step keys.data-x-<name>-<field> on slides and elements, and in narration.md as extension lines (§8.2). Producers MAY fold them into extensions.<name> at export.requires and a new version of this specification.extensions.md with a link to their definition.signatures-v1, draft)Draft. Not part of the 1.0 conformance requirements until this note is removed. Interoperability with C2PA Content Credentials is planned for a later version (statement → claim and hard binding,
derivedFrom→ ingredient, affiliation → identity assertion).
A rights holder MAY sign a lesson so that players can show who signed it and whether it has changed since. Signatures make genuine copies verifiable; they cannot prevent modification, and an attacker can remove the signature block entirely. signatures-v1 is ignorable and MUST NOT be listed in requires.
<script type="application/vnd.egatari+json" id="egatari-signatures">{"signatures":[…]}</script>
Each entry is a JWS in JSON Serialization (RFC 7515 §7.2.2 flattened form) whose payload is the UTF-8 bytes of a statement (carried as is, so no canonicalization is needed):
| Statement member | Meaning |
|---|---|
type |
"egatari-signature/1" |
docId |
the lesson’s docId |
signedAt |
RFC 3339 time claimed by the signer |
signer |
{name, affiliation?, orcid?, keyId} — claims, verified only through §9B.3 |
fileSha256 |
SHA-256 of the whole file with the egatari-signatures element (from <script to </script> inclusive) removed |
indexSha256 |
the index’s data-sha256, if portable |
sourceSha256 |
as §3.4, if portable |
derivedFrom |
copy of metadata.derivedFrom, plus signatureDigest of the original’s signature when known |
supersedes |
digest of an earlier signature by the same signer on a previous revision |
Algorithms: EdDSA with Ed25519 (RECOMMENDED) and ES256 (P-256). Verifiers MUST support both. The protected header carries alg and kid; the JWK of the public key MAY be included as jwk.
A player that implements this feature computes fileSha256 and verifies each signature, then shows exactly one of:
fileSha256 matches; with the identity level (§9B.3).fileSha256 differs.Tier A edits by the signer SHOULD be followed by a new signature with supersedes. Edits by anyone else leave the old signature in place, which then shows modified since signing. (A later revision may sign a frame hash excluding tier A regions plus per-step narration and audio hashes, so that players can say which steps changed.)
| Level | Evidence | Shown as |
|---|---|---|
| L0 | key only (did:key), name self-declared |
“signed with key |
| L1 | the key fingerprint is published by the signer on a site they control (did:web), ORCID or researchmap; checked when online |
“ |
| L2 | an attestation by an organization (university, or an Egatari key registry) binding the key to the person and affiliation, embedded and verifiable offline | “ |
Any organization MAY issue L2 attestations; this specification names none. Which attesting organizations a player or host trusts is its own configuration (e.g. a host for one university trusts that university), and a player MUST show the attesting organization’s name rather than a generic “verified”.
Signing certificates that contain personal data beyond name and affiliation (e.g. national ID card certificates) MUST NOT be embedded. Authors SHOULD also publish each lesson’s docId and their key fingerprint on an official distribution page, so that copies can be checked against it.
| Class | Requirement summary |
|---|---|
| Document — Core / Extended | valid runtime layer; if portable, valid archive layer of that profile; readable layer (§2.1) |
| Player — Core / Extended | §9.1; resolves egatari: audio; Extended players run embedded component code |
| Editor | safe import (§11), tier A at least, §7, preserves unknown members and docId |
| Producer | writes conforming documents; §3.3 exclusions; §5 audio rules |
| LMS Binding | packages a document for a learning platform per a binding document (SCORM 1.2 in 1.0) |
| Host | a platform that accepts and plays Egatari files: §10.1 |
Conformance is tested with the fixtures in fixtures/.
A conforming host MUST:
requires), and reject unsupported requires with a clear message. Hosts whose platform limits are lower than §11’s recommendations MUST stream uploads rather than buffer them.sandbox="allow-scripts" and without allow-same-origin, and serve the file itself with a Content-Security-Policy that sandboxes it and forbids network access even when its URL is opened directly, e.g. sandbox allow-scripts; default-src 'none'; img-src data: blob:; media-src data: blob:; style-src 'unsafe-inline' data:; script-src 'unsafe-inline' blob:; font-src data:.deck-meta.completion) distinct from the host’s own grading.A host MAY supply its own controls and restrict navigation (§1.2). Conformance covers Egatari content only; other kinds of material a platform offers are out of scope.
srcdoc, or insert the file into a document. It MUST validate the index hash, blob hashes, MIME types, and archive paths (§3.3) before constructing any workspace.Content-Security-Policy: sandbox with default-src 'none', nosniff, a non-HTML type and Content-Disposition: attachment). Because navigation is an exfiltration channel even under connect-src 'none' (the requested URL carries data), the embedding page MUST restrict frame-src to the exact preview endpoint (a CSP source ending in / is a prefix match), so that the preview cannot navigate itself to the editor’s origin, data:, blob: or external URLs. A preview document MUST NOT be served for top-level navigation (where no embedder’s policy applies); servers SHOULD require evidence of the controlled embedding context (e.g. a per-session capability issued to the embedding page, or Sec-Fetch-Dest: iframe).signatures-v1 (§9B, draft) lets a rights holder sign a lesson so that players can show whether a copy is unmodified since signing and who signed it; signatures make genuine copies verifiable, they do not make forgery impossible. Until then, players and hosts MUST NOT present any lesson as verified or authentic, and metadata such as creators MUST be described as claims.text/html. Extension .ega.html.application/vnd.egatari+json (to be registered with IANA when 1.0 is frozen).deck-meta.format == "egatari" or an egatari-index block.docs/contract.md), component/theme dependency manifests and hash scope, schemas for every runtime block, archive↔runtime consistency rules.file://; download as the baseline save path.signatures-v1: what is signed (index and runtime layer), interaction with tier A edits and derived lessons, key identity (e.g. did:web, GitHub keys, Sigstore, C2PA certificates), timestamps and long-term verification.