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)
| Property | Type | Default | Description |
|---|---|---|---|
id | Identifier | — | Optional explicit ID (the map key is the canonical ID) |
title | LocalizedString | — | Panel title (authoring/navigation aid) |
description | LocalizedString | — | Panel description |
durationMs | number ≥ 100 | — | Autoplay dwell time in ms; overrides the global settings.ui.secondsPerPanel for this panel |
formatViews | object (format → PanelFormatView) | — | Per-output-format view behavior — see below |
layers | Layer[] | — | Visual/audio content stack |
animations | PanelAnimations | — | Layer keyframes and/or a viewport (Ken Burns-style) move — see Animations |
speechBubbles | SpeechBubble[] (unique) | — | Dialogue balloons — see Speech Bubbles |
hotspots | Hotspot[] (unique) | — | Interactive regions — see Hotspots |
audio | AudioTrack[] | — | Panel-scoped audio — see Audio |
video | VideoLayer[] | — | Panel-scoped video layers — see Video |
plugins | PluginInstance[] | — | Embedded plugin instances — see below |
accessibility | AccessibilityHints | — | Alt text and labels — see below |
shareable | boolean | true | Whether the panel may be shared (social/embed) |
age_rating_override | string | — | Overrides meta.age_rating for this panel |
variants | PanelVariant[] (unique) | — | Conditional overrides of panel content — see Variants |
preloadHints | Identifier[] (unique) | — | Asset catalog IDs to prefetch when this panel becomes likely |
memoryBudgetHint | number ≥ 0 | — | Advisory memory budget for the panel's assets |
contentWarnings | Identifier[] (unique) | — | References into meta.content_warnings |
placement | object | — | 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:
| Property | Type | Description |
|---|---|---|
minimalFocusRect | NormalizedRect | The 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) |
allowPageView | boolean | Whether this panel may appear in page view for this format |
allowPanelView | boolean | Whether this panel may appear in panel view for this format |
Format-wide defaults come from settings.outputPresets; formatViews refines them per panel.
AccessibilityHints
| Property | Type | Description |
|---|---|---|
alt | LocalizedString | Alternative description of the whole panel for screen readers |
labels | object (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):
| Property | Type | Required | Description |
|---|---|---|---|
pluginId | Identifier | Yes | Which plugin to instantiate |
instanceId | Identifier | Yes | Unique instance ID within the work |
payloadId | Identifier | — | Reference to a pluginPayload catalog asset |
props | JsonValue | — | Arbitrary configuration passed to the plugin |
sandbox | string | — | iframe | worker — isolation mode |
stateVariables | string[] (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
}
}
Related pages
Layers
The six layer kinds and their positioning model.
Speech Bubbles
Balloon types, tails, and the styling cascade.
Hotspots
Shapes and the five action types.
Animations
Layer keyframes and viewport moves (PanelAnimations).
Variants
Conditional panel overrides (PanelVariant).
Audio & Video
Panel audio tracks and video layers.