Manifest ReferencePanels

Panels

The Panel object — layers, speech bubbles, hotspots, audio, video, format views, accessibility hints, variants, and performance hints.

A Panel is the atomic unit of a PanelWave story: one frame of the comic, composed of stacked layers, optionally decorated with speech bubbles, hotspots, audio, video, and plugins. Panels live in a chapter's panels map (panel ID → Panel) and are wired together by the chapter graph.

All Panel properties are optional at the schema level — the panel's identity is its key in the panels map.

Properties (Panel)

PropertyTypeDefaultDescription
idIdentifier—Optional explicit ID (the map key is the canonical ID)
titleLocalizedString—Panel title (authoring/navigation aid)
descriptionLocalizedString—Panel description
durationMsnumber ≥ 100—Autoplay dwell time in ms; overrides the global settings.ui.secondsPerPanel for this panel
formatViewsobject (format → PanelFormatView)—Per-output-format view behavior — see below
layersLayer[]—Visual/audio content stack
animationsPanelAnimations—Layer keyframes and/or a viewport (Ken Burns-style) move — see Animations
speechBubblesSpeechBubble[] (unique)—Dialogue balloons — see Speech Bubbles
hotspotsHotspot[] (unique)—Interactive regions — see Hotspots
audioAudioTrack[]—Panel-scoped audio — see Audio
videoVideoLayer[]—Panel-scoped video layers — see Video
pluginsPluginInstance[]—Embedded plugin instances — see below
accessibilityAccessibilityHints—Alt text and labels — see below
shareablebooleantrueWhether the panel may be shared (social/embed)
age_rating_overridestring—Overrides meta.age_rating for this panel
variantsPanelVariant[] (unique)—Conditional overrides of panel content — see Variants
preloadHintsIdentifier[] (unique)—Asset catalog IDs to prefetch when this panel becomes likely
memoryBudgetHintnumber ≥ 0—Advisory memory budget for the panel's assets
contentWarningsIdentifier[] (unique)—References into meta.content_warnings
placementobject—Free-form editor placement data (normalized x/y/w/h, rotation, grid rows/columns) written by the CMS; player positioning comes from page layouts

PanelFormatView

formatViews maps an OutputFormat name to view constraints for that format:

PropertyTypeDescription
minimalFocusRectNormalizedRectThe region that must stay visible when the format crops the panel (e.g. mobile portrait showing a landscape panel). @panelwave/player centres the crop on it in panel view (from the release after 1.2.0)
allowPageViewbooleanWhether this panel may appear in page view for this format
allowPanelViewbooleanWhether this panel may appear in panel view for this format

Format-wide defaults come from settings.outputPresets; formatViews refines them per panel.

AccessibilityHints

PropertyTypeDescription
altLocalizedStringAlternative description of the whole panel for screen readers
labelsobject (key → LocalizedString)Named labels for parts of the panel (e.g. ARIA labels for regions)

Asset-level accessibility (alt, caption, transcript) lives on catalog items; hotspots have their own ariaLabel.

PluginInstance

Embeds custom interactive content (also used by PluginLayer — see Layers):

PropertyTypeRequiredDescription
pluginIdIdentifierYesWhich plugin to instantiate
instanceIdIdentifierYesUnique instance ID within the work
payloadIdIdentifier—Reference to a pluginPayload catalog asset
propsJsonValue—Arbitrary configuration passed to the plugin
sandboxstring—iframe | worker — isolation mode
stateVariablesstring[] (unique)—Variables the plugin may access; each must match ^plugin\.

Example

A choice panel combining most features (adapted from the bundled branching sample):

{
  "p1-3": {
    "title": { "en-US": "Fork in the Alley", "de-DE": "Gabelung in der Gasse" },
    "durationMs": 6000,
    "formatViews": {
      "mobile-portrait": {
        "minimalFocusRect": { "x": 0.1, "y": 0.1, "w": 0.8, "h": 0.8 },
        "allowPageView": false,
        "allowPanelView": true
      }
    },
    "layers": [
      { "kind": "image", "id": "ly-p1-3-bg", "assetId": "img-p1-3", "z": 0 }
    ],
    "speechBubbles": [
      {
        "id": "sb-p1-3-1",
        "characterId": "char-mira",
        "text": { "en-US": "We must choose a path." },
        "shape": { "x": 0.6, "y": 0.15, "w": 0.3, "h": 0.18 }
      }
    ],
    "hotspots": [
      {
        "id": "hs-left",
        "shape": { "type": "rect", "x": 0.05, "y": 0.4, "w": 0.4, "h": 0.4 },
        "label": { "en-US": "Go left" },
        "action": {
          "type": "goTo",
          "to": "p1-4",
          "mutations": [ { "op": "set", "var": "path.choice", "value": "left" } ]
        }
      }
    ],
    "audio": [
      { "assetId": "sfx-rain", "role": "ambient", "loop": true, "gain": 0.3 }
    ],
    "accessibility": {
      "alt": { "en-US": "A rain-soaked alley splits left and right." }
    },
    "preloadHints": ["img-p1-4", "img-overlay-left", "img-overlay-right"],
    "contentWarnings": [],
    "shareable": true
  }
}