This specification is provisional. It lets the Reader and the authoring workflow be tested together, but it is not yet a stable interchange or archival standard. Fields, limits, profiles, and migration rules may change before a stable release.
Think of iComic as a page-first interactive-comic format, not a video file wearing a comic interface. Each page owns a set of layered panels and moves through discrete reveal steps.
Animation, audio, backgrounds, captions, haptics, and optional depth can respond to those steps. The essential page must still remain readable without any of them.
{
"format": "valentine.interactive-comic",
"schemaVersion": "0.3.1"
}The main development container is .icomic. Underneath
the custom extension, it is an ordinary ZIP archive. Ordinary
.zip files also remain accepted during development.
Draft 0.3.1 adds two things: a bounded media catalog and
ordered local alternatives. The current Reader can still read drafts
0.3 and 0.2, but those drafts do not gain
guarantees for fields they never declared.
The formats named here are first-class options, not universal winners. Results depend on the source, content, encoder, chosen quality, target browser and operating system, and the author’s experience and preference.
In practice, flat line art may need a different choice from painted pages, and voice may need a different choice from ambience or short motion panels.
Draft 0.3.1 standardizes the combinations worth
optimizing deeply while keeping fallbacks explicit and ordered. The
format and Reader may add compatibility layers where practical, but
packages cannot assume that every browser includes every decoder. The
Reader will not silently download a heavyweight decoder just to play an
optional variant.
Current first-class delivery targets are:
| Role | Preferred high-efficiency choices | Practical compatibility choices |
|---|---|---|
| Static image | AVIF or WebP | WebP, JPEG, or PNG where appropriate |
| Lossy audio | Opus or AAC-LC in M4A/MP4 | MP3 or browser-supported Ogg audio |
| Lossless audio | FLAC | PCM WAV when size is deliberately accepted |
| Video | AV1 in WebM/MP4 | H.264/AVC in MP4 |
WavPack and H.266/VVC can be useful authoring/master
inputs, especially when their quality-to-size trade-offs suit
the source. They are not 0.3.1 browser-delivery promises.
An authoring tool may accept them, then produce the ordered web-delivery
variants declared in the package.
Animated AVIF and GIF remain outside the current static-image contract.
{
"format": "valentine.interactive-comic",
"schemaVersion": "0.3.1",
"id": "my-comic",
"title": "My Comic",
"pages": [
{
"id": "page-1",
"panels": [
{
"id": "page-1-art",
"type": "image",
"src": "assets/pages/page-001.webp",
"alt": "Describe the page art here."
}
]
}
]
}The assets catalog is optional. A single-source manifest
remains valid, and its src fields resolve exactly as
before.
my-comic.icomic # ZIP file with a custom extension
|-- comic.json # REQUIRED at archive root
|-- assets/
| |-- panels/
| | |-- bush.avif
| | `-- bush.webp
| |-- backgrounds/
| | |-- dusk.avif
| | `-- dusk.webp
| |-- audio/
| | |-- rain.opus
| | |-- rain.m4a
| | `-- branch-snap.flac
| `-- video/
| |-- branch-rattle-av1.webm
| `-- branch-rattle-h264.mp4
`-- README.txt # Optional production notes; ignored
The current archive rules are straightforward:
comic.json, at the archive
root. story.json, manifest.json, and
reader.json remain development aliases./-separated, and
case-sensitive../ is normalized away.The Reader opens comic.json first. It then resolves
media for the page being viewed, and it may warm a bounded part of the
next page while idle.
For ZIP compression, use this working split:
comic.json and other
small JSON or text files.Keep authoring masters outside the .icomic. The future
packer should choose this profile deterministically and record every
decision in its report.
assetsassets is an optional array of logical media assets. One
manifest may declare at most 10,000 assets. Each asset may declare at
most four ordered variants.
{
"assets": [
{
"id": "page-1-art",
"kind": "image",
"fallback": "assets/pages/page-001.webp",
"delivery": {
"profile": "web-balanced",
"selection": "first-supported"
},
"variants": [
{
"src": "assets/pages/page-001.avif",
"type": "image/avif"
},
{
"src": "assets/pages/page-001.webp",
"type": "image/webp"
}
]
}
]
}Each asset uses these fields:
| Field | Type | Meaning |
|---|---|---|
id |
string | Unique logical asset ID. |
kind |
image, audio, or video |
Required role shared by every variant. |
fallback |
relative path | Required baseline source and one of the declared variant paths. |
delivery |
object | Optional profile label plus selection behavior for this logical asset. |
variants |
array | One to four ordered local alternatives. |
Each variant uses these fields:
| Field | Type | Meaning |
|---|---|---|
src |
relative path | Required local sibling/archive path. |
type |
MIME string | Required declared media type, consistent with kind and
the accepted file type. |
codecs |
string | Optional codec token or codec list used by browser capability checks, especially for audio and video. |
There are three important path rules:
src within one asset.A declared type is a claim the package must satisfy. It cannot be used to disguise an extension.
delivery.profile is a short authoring label recorded for
inspection. It does not grant features or choose a decoder.
The only 0.3.1 selection mode is
first-supported. Variants are tried in manifest order,
subject to the browser’s declared support and successful decoding. If
delivery data is omitted, it normalizes to an empty profile and
first-supported.
An image background, image/video panel, or audio cue may add an
asset field:
{
"type": "image",
"asset": "page-1-art",
"src": "assets/pages/page-001.webp"
}The reference must exist, and its kind must match how it
is used:
image.video.audio.For draft compatibility, src remains required on
media-bearing content. It must exactly equal the referenced asset’s
fallback.
This gives older Readers a usable baseline file, while
0.3.1 Readers may select a better supported variant. The
fallback is not a fifth hidden variant.
For page media and pixel budgets, all alternatives count as one logical asset. The largest alternative is used for that logical asset’s accounting instead of summing equivalent encodings. Every stored byte still counts toward archive and entry limits.
JSON cannot contain comments, so notes follow the example.
{
"format": "valentine.interactive-comic",
"schemaVersion": "0.3.1",
"id": "bush-at-dusk-test",
"title": "Bush at Dusk — Interaction Test",
"creator": "ValentineDesigns",
"description": "A non-canon technical sample for reveals and media selection.",
"language": "en",
"direction": "ltr",
"assets": [
{
"id": "dusk-background",
"kind": "image",
"fallback": "assets/backgrounds/dusk.webp",
"delivery": {
"profile": "web-balanced",
"selection": "first-supported"
},
"variants": [
{ "src": "assets/backgrounds/dusk.avif", "type": "image/avif" },
{ "src": "assets/backgrounds/dusk.webp", "type": "image/webp" }
]
},
{
"id": "bush-art",
"kind": "image",
"fallback": "assets/panels/bush.webp",
"delivery": {
"profile": "web-balanced",
"selection": "first-supported"
},
"variants": [
{ "src": "assets/panels/bush.avif", "type": "image/avif" },
{ "src": "assets/panels/bush.webp", "type": "image/webp" }
]
},
{
"id": "rain-bed",
"kind": "audio",
"fallback": "assets/audio/rain.m4a",
"delivery": {
"profile": "web-balanced",
"selection": "first-supported"
},
"variants": [
{
"src": "assets/audio/rain.opus",
"type": "audio/ogg",
"codecs": "opus"
},
{
"src": "assets/audio/rain.m4a",
"type": "audio/mp4",
"codecs": "mp4a.40.2"
}
]
},
{
"id": "branch-snap",
"kind": "audio",
"fallback": "assets/audio/branch-snap.flac",
"delivery": {
"profile": "lossless-effects",
"selection": "first-supported"
},
"variants": [
{
"src": "assets/audio/branch-snap.flac",
"type": "audio/flac"
}
]
},
{
"id": "branch-motion",
"kind": "video",
"fallback": "assets/video/branch-rattle-h264.mp4",
"delivery": {
"profile": "web-balanced",
"selection": "first-supported"
},
"variants": [
{
"src": "assets/video/branch-rattle-av1.webm",
"type": "video/webm",
"codecs": "av01.0.08M.08"
},
{
"src": "assets/video/branch-rattle-h264.mp4",
"type": "video/mp4",
"codecs": "avc1.4D401F"
}
]
}
],
"experimental": {
"customContainer": true,
"animatedPanels": true,
"audioCues": true,
"dynamicBackgrounds": true,
"depth": true,
"timedReveals": true
},
"presentation": {
"fit": "contain",
"startMode": "panel",
"pageGap": 18
},
"pages": [
{
"id": "dusk-page",
"label": "Dusk / three reveals",
"aspectRatio": "2/3",
"background": {
"type": "image",
"asset": "dusk-background",
"src": "assets/backgrounds/dusk.webp",
"fit": "cover",
"depth": -2
},
"cues": [
{
"id": "rain-bed-cue",
"type": "audio",
"trigger": "enter",
"asset": "rain-bed",
"src": "assets/audio/rain.m4a",
"volume": 0.3,
"loop": true,
"channel": "ambience"
}
],
"panels": [
{
"id": "bush",
"type": "image",
"asset": "bush-art",
"src": "assets/panels/bush.webp",
"alt": "A dense bush moving against the still dusk.",
"frame": {
"x": 5,
"y": 8,
"width": 90,
"height": 46,
"rotation": 0,
"radius": 1.5
},
"fit": "cover",
"depth": 1,
"z": 2,
"reveal": {
"atStep": 0,
"transition": "fade",
"durationMs": 500
},
"animation": {
"type": "drift",
"durationMs": 8000,
"amount": 0.35
}
},
{
"id": "branches",
"type": "video",
"asset": "branch-motion",
"src": "assets/video/branch-rattle-h264.mp4",
"alt": "Branches rattle and settle.",
"frame": { "x": 5, "y": 8, "width": 90, "height": 46 },
"fit": "cover",
"depth": 2,
"z": 3,
"reveal": {
"atStep": 1,
"transition": "focus",
"durationMs": 240
},
"playback": {
"autoplay": true,
"muted": true,
"loop": false,
"playsInline": true
},
"cues": [
{
"id": "branch-snap-cue",
"type": "audio",
"trigger": "reveal",
"asset": "branch-snap",
"src": "assets/audio/branch-snap.flac",
"delayMs": 80,
"volume": 0.65,
"channel": "effects"
},
{
"id": "branch-caption",
"type": "caption",
"trigger": "reveal",
"text": "Branches rattle.",
"durationMs": 1800
}
]
},
{
"id": "figure",
"type": "image",
"src": "assets/panels/figure.webp",
"alt": "A tired figure rises behind the bush.",
"frame": { "x": 12, "y": 33, "width": 76, "height": 58 },
"fit": "contain",
"depth": 3,
"z": 4,
"reveal": {
"atStep": 2,
"transition": "rise",
"durationMs": 650
}
}
]
}
]
}The first image or media alternative is the preferred encoding. The
baseline fallback stays authoritative for older
Readers.
The FLAC effect uses one variant because lossless quality is intentional in that case. A package does not need to manufacture alternatives that add no practical value.
| Field | Type | Current meaning / normalization |
|---|---|---|
format |
string | Expected: valentine.interactive-comic; legacy
valentine.interactive-story remains readable during the
draft. |
schemaVersion |
string | Current draft: 0.3.1; 0.3 and
0.2 remain accepted legacy drafts. |
id |
string | Stable work identifier; unsafe characters normalize to
-. |
title |
string | Defaults to Untitled comic, maximum 240
characters. |
creator |
string | Optional, maximum 240 characters. |
description |
string | Optional, maximum 1,000 characters. |
language |
string | BCP-47-like language hint; defaults to en, maximum 35
characters. |
direction |
ltr or rtl |
Defaults to ltr; full RTL UX validation remains
open. |
context |
array | Optional Reader-wide author context and linked terms, maximum 100 entries. |
assets |
array | Optional bounded catalog of logical media and ordered variants. |
experimental |
object | Declares provisional capabilities expected by the work. |
presentation |
object | Default fit, mode, and page gap. |
pages |
array | Required, 1–10,000 pages. |
Each context entry uses a stable id, short
label, title, and concise body.
An optional exact term links matching words in text panels
to a hover/focus preview and click/tap appendix entry; optional
details supplies the expanded explanation. The same
text-only shape is used by prose Reader documents. Entries without
term remain available from Comic margins.
experimentalThe current boolean declarations are:
customContaineranimatedPanelsaudioCuesdynamicBackgroundsdepthtimedRevealsThese fields describe expectations. They are not security permissions. A Reader may disable an enhancement, but it must still expose the readable core.
presentation| Field | Allowed values | Default |
|---|---|---|
fit |
contain, width, height |
contain |
startMode |
panel, page |
panel |
pageGap |
number, clamped 0–100 | 18 |
| Field | Type | Current meaning |
|---|---|---|
id |
string | Unique across the comic. |
label |
string | Timeline label, maximum 120 characters. |
aspectRatio |
number or ratio string | Examples: 0.6667, 2/3, 2:3;
defaults to 2:3. |
background |
object | Page-level color, gradient, image, or provisional dynamic background. |
panels |
array | Layered page content. |
cues |
array | Page-scoped events such as ambience on entry. |
{
"type": "image",
"asset": "dusk-background",
"src": "assets/backgrounds/dusk.webp",
"fit": "cover",
"depth": -1
}Recognized background types are color,
gradient, image, and dynamic. The
optional asset field is valid only for an image
background.
dynamic remains a placeholder category. Its
value accepts only a narrow, sanitized color/gradient
grammar. Network-capable CSS functions and URL/data/blob schemes fall
back safely.
| Field | Type | Current meaning |
|---|---|---|
id |
string | Unique within its page. |
type |
enum | image, text, video,
gif, pdf-page, or reserved
canvas. |
asset |
string | Optional logical asset reference for an image or video panel. |
src |
string | Relative baseline media path; must equal the asset fallback when
asset is present. |
alt |
string | Nonvisual description, maximum 500 characters. |
text |
string | Plain text-panel content, maximum 10,000 characters. |
frame |
object | Percentage geometry, rotation, and corner radius. |
fit |
enum | contain, cover, or fill;
defaults to cover. |
depth |
number | Optional pointer/tilt depth, clamped -10 to 10. |
z |
integer | Layer order, clamped -100 to 1,000. |
reveal |
object | Discrete step, transition, and duration. |
animation |
object | Optional decorative animation. |
playback |
object | Video playback hints. |
cues |
array | Events associated with this panel/reveal. |
Frames use page-relative percentages. Reveal steps are nonnegative integers.
Recognized transitions are:
nonefaderiseslide-leftslide-rightinkfocusDecorative animation types are:
nonedriftpulsefloatpanbreatheDecorative animation must not carry information that disappears under reduced motion.
Playback hints are autoplay, muted,
loop, and playsInline. Browser autoplay and
codec rules override these hints.
{
"id": "soft-signal",
"type": "audio",
"trigger": "reveal",
"asset": "signal-audio",
"src": "assets/audio/signal.m4a",
"text": "Optional visible caption",
"delayMs": 0,
"volume": 0.65,
"loop": false,
"channel": "effects"
}Recognized cue types are:
audiotonebackgroundvibratecaptionOnly an audio cue may reference an audio asset. The common current
triggers are enter and reveal.
Channel replacement, crossfade, persistence, loop lifetime, and mastering metadata remain provisional.
The current Reader dispatches cues for every panel revealed on the same step. It cancels pending delayed cues when another reveal begins or the page changes.
The current rejection ceilings are:
These are safety limits, not performance promises. A valid package may still be too demanding for a particular phone, browser, or operating system.
A future conforming Reader should:
See Authoring and encoding for the planned content-aware encoding, validation, packaging, and upload workflow.
The iComic specification and observable behavior may be discussed publicly. The Gremmoire Reader and its authoring/tooling source remain closed.
The implementation is tuned around ValentineDesigns’ work and publishing workflow. Publishing it as a general-purpose codebase would imply portability and support that it is not designed to provide.
For a bespoke Reader or authoring workflow adapted to another site, contact [email protected].
A recipient who receives a bespoke source copy is free to host, adapt, modify, and otherwise use that supplied source as they wish. This permission applies to the delivered bespoke copy. It does not make the Gremmoire Reader or the general toolchain open source.