iComic format draft 0.2 — ValentineDesigns Comic Reader

iComic format — provisional draft 0.2

This specification is provisional. It exists so the reader can be built and tested while the real authoring workflow is designed. It is not a stable interchange or archival standard. Field names, meanings, extensions, limits, and migration rules may change without backward compatibility.

The working idea is a page-first interactive comic, not a video file with a comic skin. A page owns layered panels. Reading advances through discrete reveal steps. Animation, audio, backgrounds, captions, haptics, and optional depth can respond to those steps, but the page remains readable without them.

Working identity

{
  "format": "valentine.interactive-comic",
  "schemaVersion": "0.2"
}

The primary development container is .icomic, an ordinary ZIP archive. Written prose will use a separate, lighter format later. Ordinary .zip is also accepted during development.

Minimum manifest

{
  "format": "valentine.interactive-comic",
  "schemaVersion": "0.2",
  "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 current validator requires at least one page. Media panels such as image, gif, and video should have src; missing sources currently produce a warning rather than always rejecting the entire comic.

my-comic.icomic                 # ZIP file with a custom extension
|-- comic.json                 # REQUIRED at archive root
|-- assets/
|   |-- panels/
|   |   |-- bush.webp
|   |   `-- figure.webp
|   |-- backgrounds/
|   |   `-- dusk.webp
|   |-- audio/
|   |   |-- rain-loop.ogg
|   |   |-- branch-snap.wav
|   |   `-- sigh.wav
|   `-- video/
|       `-- branch-rattle.webm
`-- README.txt                 # Optional human production notes; ignored

Archive rules in the current loader:

Do not nest another ZIP as a content mechanism. Nested-container semantics and their resource limits are undefined.

Packaging performance (current guidance)

The reader opens comic.json first, then resolves archive media only for the page being viewed. It can warm the next page when the browser is idle. Keep that path fast by using a mixed ZIP profile:

This is implementation guidance, not a finalized interchange rule. The future packer should make the profile deterministic and record it in its report.

Fully annotated example

JSON cannot contain comments, so annotations follow the example.

{
  "format": "valentine.interactive-comic",
  "schemaVersion": "0.2",
  "id": "bush-at-dusk-test",
  "title": "Bush at Dusk — Interaction Test",
  "creator": "ValentineDesigns",
  "description": "A non-canon technical sample for reveals, sound, and depth.",
  "language": "en",
  "direction": "ltr",

  "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",
        "src": "assets/backgrounds/dusk.webp",
        "fit": "cover",
        "depth": -2
      },

      "cues": [
        {
          "id": "rain-bed",
          "type": "audio",
          "trigger": "enter",
          "src": "assets/audio/rain-loop.ogg",
          "volume": 0.3,
          "loop": true,
          "channel": "ambience"
        }
      ],

      "panels": [
        {
          "id": "bush",
          "type": "image",
          "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": "branch-motion",
          "type": "video",
          "src": "assets/video/branch-rattle.webm",
          "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",
              "type": "audio",
              "trigger": "reveal",
              "src": "assets/audio/branch-snap.wav",
              "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
          },
          "cues": [
            {
              "id": "sigh",
              "type": "audio",
              "trigger": "reveal",
              "src": "assets/audio/sigh.wav",
              "delayMs": 360,
              "volume": 0.55,
              "channel": "voice"
            }
          ]
        }
      ]
    }
  ]
}

This example shows the intended direction. It does not mean every lifecycle detail—especially active-channel replacement, loop lifetime, video end behavior, and captions—is frozen or production-complete.

Top-level fields

Field Type Current meaning / normalization
format string Expected: valentine.interactive-comic. The older valentine.interactive-story ID remains readable during the draft.
schemaVersion string Current draft: 0.2. Another value warns; no migration system exists yet.
id string Stable work identifier. Unsafe characters normalize to -.
title string Required in practice; defaults to Untitled comic, max 240 characters.
creator string Optional, max 240 characters.
description string Optional, max 1,000 characters.
language string BCP-47-like language hint; defaults to en, current max 35 characters.
direction ltr or rtl Defaults to ltr; full RTL UX validation is still open.
experimental object Declares which provisional capabilities the work expects. Informational in parts of the current runtime.
presentation object Default fit/mode/page gap.
pages array Required, at least 1 and no more than 10,000.

experimental

All current values normalize to booleans:

These are capability declarations, not security permissions. A reader may disable any enhancement and must still expose the readable core.

presentation

Field Allowed values Default
fit contain, width, height contain
startMode panel, page panel
pageGap number, currently clamped 0–100 18

pageGap is normalized for future spread/strip presentation but the current single-page stage does not visibly consume it yet.

Page fields

Field Type Current meaning
id string Unique across the comic. Duplicate page IDs reject the manifest.
label string Human timeline label, max 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. May currently be empty, though publishing validation should warn.
cues array Page-scoped events such as ambience on entry.

Page background

{
  "type": "gradient",
  "value": "linear-gradient(145deg, #090b10, #171a24)",
  "src": "",
  "fit": "cover",
  "depth": -1
}

Recognized type values: color, gradient, image, dynamic.

dynamic is a placeholder category. value currently accepts only a sanitized color or linear/radial/conic gradient; network-capable CSS functions and URL/ data/blob schemes fall back to a safe local gradient. Before a stable release this should become typed color-stop/effect data rather than an open CSS-like string. depth is clamped from -10 to 10, but the current renderer does not move the background as a separate depth plane yet.

Panel fields

Field Type Current meaning
id string Unique within its page. Duplicate panel IDs on one page reject the manifest.
type enum image, text, video, gif, pdf-page, or reserved canvas. Unknown becomes image.
src string Relative local asset path for media panels.
alt string Nonvisual description, max 500 characters.
text string Plain text-panel content, max 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 1000.
reveal object Discrete step, transition, and transition duration.
animation object Optional decorative animation.
playback object Video/media playback hints.
cues array Events associated with this panel/reveal.

Frame

{
  "x": 10,
  "y": 20,
  "width": 80,
  "height": 45,
  "rotation": 0,
  "radius": 2
}

All geometry currently uses page-relative percentages. Defaults fill the page. The lab permits x / y from -100 to 200 and width / height up to 300 so overscan and animation can be tested. A publishing validator will likely need stricter “visible content” rules.

Reveal

{
  "atStep": 2,
  "transition": "rise",
  "durationMs": 520
}

Several panels may share the same atStep; they reveal together. Missing step numbers are allowed. The page maximum is the highest panel step.

Animation

{
  "type": "breathe",
  "durationMs": 8000,
  "amount": 0.6
}

Recognized types: none, drift, pulse, float, pan, breathe. Duration is currently clamped 250–120,000 ms and amount 0–10. Authors must not use animation to carry information that disappears when reduced motion is on. The lab normalizes amount, but its current CSS motion presets do not scale their travel/intensity from that field yet.

Playback

{
  "autoplay": true,
  "muted": true,
  "loop": false,
  "playsInline": true
}

Defaults are conservative: no autoplay, muted unless explicitly false, no loop, and inline playback unless explicitly false. Browser autoplay/codec rules still override these hints.

Cue fields

{
  "id": "soft-signal",
  "type": "audio",
  "trigger": "reveal",
  "src": "assets/audio/signal.ogg",
  "text": "Optional visible caption",
  "color": "#1d2938",
  "frequency": 180,
  "durationMs": 1200,
  "delayMs": 0,
  "volume": 0.65,
  "loop": false,
  "channel": "effects"
}

Recognized cue types:

Current common triggers are enter for page cues and reveal for panel cues. The trigger string is not yet a closed enum, which is convenient for lab work but unsuitable for a stable authoring contract.

The current lab dispatches cues for every panel that appears on the same reveal step, not only the last panel in that step. Pending delayed cues are cancelled when another reveal begins or the page changes. Those are now dependable lab behaviors, but their precise compatibility promise still needs to be written into a stable schema version.

Normalization limits:

Audio/channel rules still to decide

The next format revision must define:

Until then, audio timing in a draft container is a test instruction, not a mastered playback guarantee.

Reader conformance principles (proposed)

A conforming future reader should:

  1. Show every page’s essential visual/text content without sound, haptics, animation, depth, or sensor permission.
  2. Preserve author-defined reveal order while allowing the reader to reveal a full page.
  3. Respect reduced motion and mute settings immediately.
  4. Provide keyboard/button navigation independent of gestures.
  5. Never execute arbitrary scripts, HTML, CSS, plugins, or network calls from an iComic container.
  6. Warn clearly about unsupported required features instead of silently dropping comic-critical content.
  7. Expose readable alt text/captions in a defined order.
  8. Tear down media, timers, sensors, and temporary URLs when leaving a work.

These principles are proposals and will become binding only after the format is versioned beyond the lab draft.

Packaging checklist (manual, for now)

An automated authoring validator/packer is on the backlog. It should eventually produce reproducible archives, asset hashes, codec reports, accessibility warnings, and a compatibility summary.

Open format questions