{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://tashiikid.github.io/loopweft/schema/v1.json",
  "title": "Loopweft project (format version 1)",
  "description": "A Loopweft project: a causal map, its evidence and its story.",
  "type": "object",
  "properties": {
    "$schema": {
      "description": "The JSON Schema this file follows (for editors).",
      "type": "string"
    },
    "format": {
      "type": "string",
      "const": "loopweft"
    },
    "version": {
      "type": "number",
      "const": 1
    },
    "meta": {
      "$ref": "#/$defs/Meta"
    },
    "theme": {
      "default": {
        "base": "cinematic-dark"
      },
      "$ref": "#/$defs/Theme"
    },
    "types": {
      "default": {
        "categories": [],
        "roles": [],
        "linkTypes": [],
        "fields": []
      },
      "$ref": "#/$defs/Types"
    },
    "variables": {
      "default": [],
      "type": "array",
      "items": {
        "$ref": "#/$defs/Variable"
      }
    },
    "links": {
      "default": [],
      "type": "array",
      "items": {
        "$ref": "#/$defs/Link"
      }
    },
    "layout": {
      "default": {
        "positions": {},
        "groups": [],
        "bends": {}
      },
      "$ref": "#/$defs/Layout"
    },
    "loops": {
      "default": [],
      "type": "array",
      "items": {
        "$ref": "#/$defs/Loop"
      }
    },
    "sources": {
      "default": [],
      "type": "array",
      "items": {
        "$ref": "#/$defs/Source"
      }
    },
    "story": {
      "default": {
        "scenes": []
      },
      "$ref": "#/$defs/Story"
    },
    "assets": {
      "default": {},
      "description": "Embedded images, by id.",
      "type": "object",
      "propertyNames": {
        "$ref": "#/$defs/Id"
      },
      "additionalProperties": {
        "type": "string",
        "pattern": "^data:image\\/(png|jpeg|gif|webp|svg\\+xml);base64,[A-Za-z0-9+/]+=*$"
      }
    }
  },
  "required": [
    "format",
    "version",
    "meta"
  ],
  "additionalProperties": false,
  "$defs": {
    "Meta": {
      "type": "object",
      "properties": {
        "title": {
          "type": "string",
          "minLength": 1,
          "description": "The title of the map and its story."
        },
        "description": {
          "description": "What the map is and is not, e.g. \"a qualitative synthesis, not a model\".",
          "type": "string",
          "minLength": 1
        },
        "author": {
          "type": "string",
          "minLength": 1
        },
        "course": {
          "type": "string",
          "minLength": 1
        },
        "created": {
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?(Z|[+-]\\d{2}:\\d{2}))?$"
        },
        "modified": {
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?(Z|[+-]\\d{2}:\\d{2}))?$"
        }
      },
      "required": [
        "title"
      ],
      "additionalProperties": false,
      "description": "About the project."
    },
    "Theme": {
      "type": "object",
      "properties": {
        "base": {
          "default": "cinematic-dark",
          "description": "The look the map is drawn in.",
          "type": "string",
          "enum": [
            "cinematic-dark",
            "light",
            "high-contrast"
          ]
        },
        "palette": {
          "description": "The palette for categories that name no colour of their own: a preset (loopweft, okabe-ito, tol-bright, tol-muted) or the id of one of `palettes`. Absent: loopweft.",
          "$ref": "#/$defs/Id"
        },
        "palettes": {
          "description": "The project's own palettes, beside the presets.",
          "type": "array",
          "items": {
            "$ref": "#/$defs/Palette"
          }
        },
        "fonts": {
          "description": "Font families for headings and body text.",
          "type": "object",
          "properties": {
            "heading": {
              "type": "string",
              "minLength": 1
            },
            "body": {
              "type": "string",
              "minLength": 1
            }
          },
          "additionalProperties": false
        }
      },
      "additionalProperties": false,
      "description": "How the project looks."
    },
    "Id": {
      "type": "string",
      "minLength": 1,
      "maxLength": 80,
      "pattern": "^[\\p{L}\\p{N}_-]+$",
      "description": "An identifier: letters, digits, \"_\" and \"-\"."
    },
    "Palette": {
      "type": "object",
      "properties": {
        "id": {
          "description": "Not a preset's id.",
          "$ref": "#/$defs/Id"
        },
        "name": {
          "type": "string",
          "minLength": 1
        },
        "colours": {
          "minItems": 2,
          "type": "array",
          "items": {
            "$ref": "#/$defs/Colour"
          },
          "description": "The colours categories take, in order."
        },
        "description": {
          "description": "What the palette is, e.g. where its colours come from.",
          "type": "string",
          "minLength": 1
        }
      },
      "required": [
        "id",
        "name",
        "colours"
      ],
      "additionalProperties": false,
      "description": "A palette the project holds itself, beside Loopweft's presets."
    },
    "Colour": {
      "type": "string",
      "pattern": "^#[0-9a-fA-F]{6}$",
      "description": "A colour as #rrggbb."
    },
    "Types": {
      "type": "object",
      "properties": {
        "categories": {
          "default": [],
          "type": "array",
          "items": {
            "$ref": "#/$defs/Category"
          }
        },
        "roles": {
          "default": [],
          "type": "array",
          "items": {
            "$ref": "#/$defs/Role"
          }
        },
        "linkTypes": {
          "default": [],
          "type": "array",
          "items": {
            "$ref": "#/$defs/LinkType"
          }
        },
        "fields": {
          "default": [],
          "type": "array",
          "items": {
            "$ref": "#/$defs/Field"
          }
        }
      },
      "additionalProperties": false,
      "description": "The vocabulary of the map: categories, roles, link types and custom fields."
    },
    "Category": {
      "type": "object",
      "properties": {
        "id": {
          "$ref": "#/$defs/Id"
        },
        "name": {
          "type": "string",
          "minLength": 1
        },
        "colour": {
          "description": "Absent: the theme palette assigns one.",
          "$ref": "#/$defs/Colour"
        },
        "description": {
          "type": "string",
          "minLength": 1
        }
      },
      "required": [
        "id",
        "name"
      ],
      "additionalProperties": false,
      "description": "A category of variables, drawn in its colour (e.g. a domain such as \"Earth systems\")."
    },
    "Role": {
      "type": "object",
      "properties": {
        "id": {
          "$ref": "#/$defs/Id"
        },
        "name": {
          "type": "string",
          "minLength": 1
        },
        "shape": {
          "type": "string",
          "enum": [
            "circle",
            "ring",
            "square",
            "rounded-square",
            "octagon",
            "diamond",
            "triangle",
            "hexagon"
          ]
        },
        "description": {
          "type": "string",
          "minLength": 1
        },
        "response": {
          "description": "True for responses: deliberate actions against the pressures, such as policies or interventions. A pulse arriving at one shows relief unless a trend says otherwise, and relief walks leave one along its - links. Absent: false.",
          "type": "boolean"
        }
      },
      "required": [
        "id",
        "name",
        "shape"
      ],
      "additionalProperties": false,
      "description": "A role a variable plays (e.g. stress, trigger, crisis), drawn as its shape."
    },
    "LinkType": {
      "type": "object",
      "properties": {
        "id": {
          "$ref": "#/$defs/Id"
        },
        "name": {
          "type": "string",
          "minLength": 1
        },
        "description": {
          "type": "string",
          "minLength": 1
        },
        "style": {
          "description": "Line style. Absent: solid.",
          "type": "string",
          "enum": [
            "solid",
            "dashed",
            "dotted"
          ]
        },
        "marker": {
          "description": "A mark at the middle of the link. Absent: none.",
          "type": "string",
          "enum": [
            "none",
            "flag"
          ]
        },
        "optional": {
          "description": "Links of an optional type (e.g. documented possible side-effects) are not part of the main structure: hidden until a step names them, and left out of automatic loop counts. Absent: false.",
          "type": "boolean"
        }
      },
      "required": [
        "id",
        "name"
      ],
      "additionalProperties": false,
      "description": "A kind of link (e.g. a mechanism such as \"cascade\" or \"side-effect\")."
    },
    "Field": {
      "type": "object",
      "properties": {
        "id": {
          "$ref": "#/$defs/Id"
        },
        "name": {
          "type": "string",
          "minLength": 1
        },
        "kind": {
          "type": "string",
          "enum": [
            "text",
            "number",
            "choice",
            "boolean"
          ]
        },
        "appliesTo": {
          "description": "Which elements may carry the field. Absent: both.",
          "type": "string",
          "enum": [
            "variables",
            "links",
            "both"
          ]
        },
        "options": {
          "description": "The choices, for a field of kind \"choice\".",
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id": {
                "$ref": "#/$defs/Id"
              },
              "name": {
                "type": "string",
                "minLength": 1
              },
              "description": {
                "type": "string",
                "minLength": 1
              }
            },
            "required": [
              "id",
              "name"
            ],
            "additionalProperties": false
          }
        },
        "description": {
          "type": "string",
          "minLength": 1
        }
      },
      "required": [
        "id",
        "name",
        "kind"
      ],
      "additionalProperties": false,
      "description": "A custom field that variables or links can carry."
    },
    "Variable": {
      "type": "object",
      "properties": {
        "id": {
          "$ref": "#/$defs/Id"
        },
        "label": {
          "type": "string",
          "minLength": 1,
          "description": "The full name."
        },
        "mapLabel": {
          "description": "A shorter name drawn on the map; \"\\n\" breaks the line.",
          "type": "string",
          "minLength": 1
        },
        "category": {
          "$ref": "#/$defs/Id"
        },
        "role": {
          "$ref": "#/$defs/Id"
        },
        "definition": {
          "description": "What the variable means: what rises or falls.",
          "type": "string",
          "minLength": 1
        },
        "size": {
          "description": "Glyph size; large marks a hub. Absent: normal.",
          "type": "string",
          "enum": [
            "normal",
            "large"
          ]
        },
        "fields": {
          "type": "object",
          "propertyNames": {
            "$ref": "#/$defs/Id"
          },
          "additionalProperties": {
            "type": [
              "string",
              "number",
              "boolean"
            ]
          },
          "description": "Values of custom fields (see `types.fields`), by field id."
        },
        "citations": {
          "default": [],
          "type": "array",
          "items": {
            "$ref": "#/$defs/Citation"
          }
        }
      },
      "required": [
        "id",
        "label"
      ],
      "additionalProperties": false,
      "description": "A variable: something that can rise or fall."
    },
    "Citation": {
      "type": "object",
      "properties": {
        "source": {
          "description": "The id of a source in `sources`.",
          "$ref": "#/$defs/Id"
        },
        "pages": {
          "description": "Pages or another locator in the source, e.g. \"7\", \"11–12\", \"e11\", \"S4\".",
          "type": "string",
          "minLength": 1,
          "maxLength": 60
        },
        "strength": {
          "description": "How strongly this source supports the element: the author's judgement.",
          "type": "string",
          "enum": [
            "strong",
            "moderate",
            "weak"
          ]
        },
        "note": {
          "description": "A figure, table or short quotation the citation points to.",
          "type": "string",
          "minLength": 1
        }
      },
      "required": [
        "source"
      ],
      "additionalProperties": false,
      "description": "A source cited for a variable, link, loop or overlay."
    },
    "Link": {
      "type": "object",
      "properties": {
        "id": {
          "$ref": "#/$defs/Id"
        },
        "from": {
          "description": "The id of the cause.",
          "$ref": "#/$defs/Id"
        },
        "to": {
          "description": "The id of the effect.",
          "$ref": "#/$defs/Id"
        },
        "sign": {
          "$ref": "#/$defs/Sign"
        },
        "type": {
          "description": "The id of a link type in `types.linkTypes`.",
          "$ref": "#/$defs/Id"
        },
        "conditional": {
          "description": "True when the effect holds only under some conditions (drawn dashed). Absent: false.",
          "type": "boolean"
        },
        "delay": {
          "description": "True when the effect takes notably long. Absent: false.",
          "type": "boolean"
        },
        "delayLabel": {
          "description": "How long, e.g. \"5 yrs\" (needs `delay`).",
          "type": "string",
          "minLength": 1
        },
        "confidence": {
          "type": "string",
          "enum": [
            "low",
            "medium",
            "high"
          ]
        },
        "weight": {
          "description": "Relative strength of influence, for line thickness. No units.",
          "type": "number",
          "minimum": 0
        },
        "rationale": {
          "description": "Why the cause moves the effect: the mechanism, in a sentence or two.",
          "type": "string",
          "minLength": 1
        },
        "fields": {
          "type": "object",
          "propertyNames": {
            "$ref": "#/$defs/Id"
          },
          "additionalProperties": {
            "type": [
              "string",
              "number",
              "boolean"
            ]
          },
          "description": "Values of custom fields (see `types.fields`), by field id."
        },
        "citations": {
          "default": [],
          "type": "array",
          "items": {
            "$ref": "#/$defs/Citation"
          }
        }
      },
      "required": [
        "id",
        "from",
        "to",
        "sign"
      ],
      "additionalProperties": false,
      "description": "A causal link: a change in `from` tends to change `to`, in the direction its sign gives."
    },
    "Sign": {
      "type": "string",
      "enum": [
        "+",
        "-",
        "?"
      ],
      "description": "+ : the target tends to move in the same direction as the source; - : the opposite; ? : unknown."
    },
    "Layout": {
      "type": "object",
      "properties": {
        "positions": {
          "default": {},
          "description": "Where each variable sits, by variable id, in map units (the full view is about 1920 × 1080).",
          "type": "object",
          "propertyNames": {
            "$ref": "#/$defs/Id"
          },
          "additionalProperties": {
            "$ref": "#/$defs/Position"
          }
        },
        "groups": {
          "default": [],
          "type": "array",
          "items": {
            "$ref": "#/$defs/Group"
          }
        },
        "bends": {
          "default": {},
          "description": "Curvature overrides by link id: the bow as a fraction of the link’s length; + bends left.",
          "type": "object",
          "propertyNames": {
            "$ref": "#/$defs/Id"
          },
          "additionalProperties": {
            "type": "number",
            "minimum": -1,
            "maximum": 1
          }
        },
        "home": {
          "description": "The whole-map view, as the camera frames it. Absent: fitted to the map.",
          "type": "object",
          "properties": {
            "x": {
              "type": "number",
              "description": "The map point at the centre of the stage."
            },
            "y": {
              "type": "number"
            },
            "zoom": {
              "type": "number",
              "exclusiveMinimum": 0,
              "description": "Stage pixels per map unit."
            }
          },
          "required": [
            "x",
            "y",
            "zoom"
          ],
          "additionalProperties": false
        }
      },
      "additionalProperties": false,
      "description": "Where things are drawn."
    },
    "Position": {
      "type": "object",
      "properties": {
        "x": {
          "type": "number"
        },
        "y": {
          "type": "number"
        },
        "labelSide": {
          "description": "Absent: placed automatically.",
          "type": "string",
          "enum": [
            "above",
            "below",
            "left",
            "right"
          ]
        },
        "pinned": {
          "description": "True: automatic layouts leave it where it is. Absent: false.",
          "type": "boolean"
        }
      },
      "required": [
        "x",
        "y"
      ],
      "additionalProperties": false,
      "description": "Where a variable sits on the map."
    },
    "Group": {
      "type": "object",
      "properties": {
        "id": {
          "$ref": "#/$defs/Id"
        },
        "category": {
          "description": "Draws the halo and label in this category’s colour.",
          "$ref": "#/$defs/Id"
        },
        "label": {
          "type": "string",
          "minLength": 1
        },
        "halo": {
          "description": "A soft circle behind the group.",
          "type": "object",
          "properties": {
            "x": {
              "type": "number"
            },
            "y": {
              "type": "number"
            },
            "r": {
              "type": "number",
              "exclusiveMinimum": 0
            }
          },
          "required": [
            "x",
            "y",
            "r"
          ],
          "additionalProperties": false
        },
        "labelAt": {
          "description": "Where the group label is drawn.",
          "type": "object",
          "properties": {
            "x": {
              "type": "number"
            },
            "y": {
              "type": "number"
            }
          },
          "required": [
            "x",
            "y"
          ],
          "additionalProperties": false
        }
      },
      "required": [
        "id"
      ],
      "additionalProperties": false,
      "description": "A visual group on the map: a halo and a label, usually for one category."
    },
    "Loop": {
      "type": "object",
      "properties": {
        "id": {
          "$ref": "#/$defs/Id"
        },
        "name": {
          "type": "string",
          "minLength": 1
        },
        "links": {
          "minItems": 1,
          "type": "array",
          "items": {
            "$ref": "#/$defs/Id"
          },
          "description": "The links of the loop, in order: each one starts where the previous one ends."
        },
        "archetype": {
          "description": "A system archetype it follows, e.g. \"Fixes that fail\".",
          "type": "string",
          "minLength": 1
        },
        "description": {
          "type": "string",
          "minLength": 1
        },
        "caution": {
          "description": "What the loop does not show, e.g. \"a conceptual archetype, not measured\".",
          "type": "string",
          "minLength": 1
        },
        "citations": {
          "default": [],
          "type": "array",
          "items": {
            "$ref": "#/$defs/Citation"
          }
        }
      },
      "required": [
        "id",
        "name",
        "links"
      ],
      "additionalProperties": false,
      "description": "A named feedback loop. Reinforcing or balancing follows from its link signs."
    },
    "Source": {
      "type": "object",
      "properties": {
        "id": {
          "$ref": "#/$defs/Id"
        },
        "label": {
          "description": "The short in-text form, e.g. \"Lawrence et al. (2024)\".",
          "type": "string",
          "minLength": 1
        },
        "apa": {
          "type": "string",
          "minLength": 1,
          "description": "The full APA 7 reference; *asterisks* mark italics."
        },
        "doi": {
          "type": "string",
          "pattern": "^10\\.\\d{4,9}\\/\\S+$"
        },
        "url": {
          "type": "string",
          "pattern": "^https?:\\/\\/\\S+$"
        },
        "licence": {
          "description": "The licence the source states, e.g. \"CC BY 4.0\".",
          "type": "string",
          "minLength": 1
        },
        "summary": {
          "description": "What the map draws from it.",
          "type": "string",
          "minLength": 1
        },
        "partOf": {
          "description": "For a supplement: the id of the work it belongs to.",
          "$ref": "#/$defs/Id"
        }
      },
      "required": [
        "id",
        "apa"
      ],
      "additionalProperties": false,
      "description": "A work the map cites."
    },
    "Story": {
      "type": "object",
      "properties": {
        "scenes": {
          "default": [],
          "type": "array",
          "items": {
            "$ref": "#/$defs/Scene"
          }
        }
      },
      "additionalProperties": false,
      "description": "The animated story: scenes of steps, one keypress each."
    },
    "Scene": {
      "type": "object",
      "properties": {
        "id": {
          "$ref": "#/$defs/Id"
        },
        "title": {
          "type": "string",
          "minLength": 1
        },
        "headline": {
          "description": "The one-line message, shown in the caption.",
          "type": "string",
          "minLength": 1
        },
        "targetSeconds": {
          "description": "Target length. Absent: untimed.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "notes": {
          "description": "Speaker notes (Markdown): private to the presenter.",
          "type": "string",
          "minLength": 1
        },
        "script": {
          "description": "A full script; \"[>]\" marks each keypress. Private to the presenter.",
          "type": "string",
          "minLength": 1
        },
        "sources": {
          "default": [],
          "description": "Sources the scene draws on beyond those its links cite.",
          "type": "array",
          "items": {
            "$ref": "#/$defs/Id"
          }
        },
        "steps": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/Step"
          }
        }
      },
      "required": [
        "id",
        "title",
        "steps"
      ],
      "additionalProperties": false,
      "description": "A scene: a run of steps with one message."
    },
    "Step": {
      "type": "object",
      "properties": {
        "id": {
          "description": "Unique within its scene.",
          "$ref": "#/$defs/Id"
        },
        "label": {
          "type": "string",
          "minLength": 1,
          "description": "A short name for the step, shown to the presenter."
        },
        "at": {
          "description": "Seconds into the scene when the step should arrive, for pacing.",
          "type": "number",
          "minimum": 0
        },
        "cue": {
          "description": "What happens on this step, for the presenter.",
          "type": "string",
          "minLength": 1
        },
        "map": {
          "$ref": "#/$defs/MapState"
        },
        "overlays": {
          "default": [],
          "type": "array",
          "items": {
            "$ref": "#/$defs/Overlay"
          }
        }
      },
      "required": [
        "id",
        "label",
        "map"
      ],
      "additionalProperties": false,
      "description": "One keypress of the story."
    },
    "MapState": {
      "type": "object",
      "properties": {
        "show": {
          "description": "False hides the whole map. Absent: true.",
          "type": "boolean"
        },
        "opacity": {
          "description": "Absent: 1.",
          "type": "number",
          "minimum": 0,
          "maximum": 1
        },
        "camera": {
          "type": "object",
          "properties": {
            "fit": {
              "description": "Fit these variables (with room for labels). Absent: the whole map.",
              "minItems": 1,
              "type": "array",
              "items": {
                "$ref": "#/$defs/Id"
              }
            },
            "frame": {
              "description": "Where on the stage the subject goes; \"inset\" shrinks the map into the centre. Absent: center.",
              "type": "string",
              "enum": [
                "full",
                "center",
                "left",
                "right",
                "lower",
                "inset"
              ]
            },
            "maxZoom": {
              "type": "number",
              "exclusiveMinimum": 0
            }
          },
          "additionalProperties": false
        },
        "variables": {
          "$ref": "#/$defs/LevelLists"
        },
        "links": {
          "description": "Links of an optional type stay hidden unless listed here.",
          "$ref": "#/$defs/LevelLists"
        },
        "labels": {
          "description": "Which variable labels show: focus = those at focus or active; view = those in view. Absent: view.",
          "type": "string",
          "enum": [
            "none",
            "focus",
            "view",
            "all"
          ]
        },
        "categoryLabels": {
          "description": "Show group labels. Absent: false.",
          "type": "boolean"
        },
        "labelText": {
          "description": "Label text for this step only, by variable id.",
          "type": "object",
          "propertyNames": {
            "$ref": "#/$defs/Id"
          },
          "additionalProperties": {
            "type": "string",
            "minLength": 1
          }
        },
        "trends": {
          "description": "▲ up, ▼ down or ± mixed, by variable id.",
          "type": "object",
          "propertyNames": {
            "$ref": "#/$defs/Id"
          },
          "additionalProperties": {
            "$ref": "#/$defs/Trend"
          }
        },
        "loops": {
          "description": "Loops whose R/B badge shows.",
          "type": "array",
          "items": {
            "$ref": "#/$defs/Id"
          }
        },
        "loopNames": {
          "description": "Loops whose name shows beside the badge.",
          "type": "array",
          "items": {
            "$ref": "#/$defs/Id"
          }
        },
        "reveal": {
          "description": "How newly shown elements arrive.",
          "type": "object",
          "properties": {
            "mode": {
              "description": "draw: new links trace in; bloom: the map fades in ring by ring.",
              "type": "string",
              "enum": [
                "draw",
                "bloom"
              ]
            },
            "from": {
              "description": "Variables the reveal grows out from.",
              "type": "array",
              "items": {
                "$ref": "#/$defs/Id"
              }
            },
            "sequence": {
              "description": "Exact order, one beat each: variable ids appear, link ids trace.",
              "type": "array",
              "items": {
                "$ref": "#/$defs/Id"
              }
            },
            "stagger": {
              "description": "Seconds between beats.",
              "type": "number",
              "minimum": 0
            },
            "delay": {
              "description": "Seconds before the first beat.",
              "type": "number",
              "minimum": 0
            }
          },
          "additionalProperties": false
        },
        "pulses": {
          "description": "Glows that travel along links.",
          "type": "object",
          "properties": {
            "hops": {
              "minItems": 1,
              "type": "array",
              "items": {
                "minItems": 1,
                "type": "array",
                "items": {
                  "$ref": "#/$defs/Id"
                }
              },
              "description": "Links pulsed together, hop after hop."
            },
            "repeat": {
              "description": "Extra passes, e.g. going round a loop again.",
              "type": "integer",
              "minimum": 0,
              "maximum": 9007199254740991
            },
            "delay": {
              "type": "number",
              "minimum": 0
            },
            "tone": {
              "description": "Absent: each pulse follows its target’s trend.",
              "type": "string",
              "enum": [
                "harm",
                "relief",
                "mixed"
              ]
            }
          },
          "required": [
            "hops"
          ],
          "additionalProperties": false
        },
        "ambient": {
          "description": "Pulses that keep wandering the lit map while the step is held, along links whose signs fit the tone.",
          "type": "object",
          "properties": {
            "from": {
              "minItems": 1,
              "type": "array",
              "items": {
                "$ref": "#/$defs/Id"
              }
            },
            "tone": {
              "type": "string",
              "enum": [
                "harm",
                "relief"
              ]
            },
            "every": {
              "description": "Seconds between walks: [min, max].",
              "type": "array",
              "prefixItems": [
                {
                  "type": "number",
                  "minimum": 0
                },
                {
                  "type": "number",
                  "minimum": 0
                }
              ],
              "items": false,
              "minItems": 2,
              "maxItems": 2
            },
            "hops": {
              "description": "Links per walk: [min, max].",
              "type": "array",
              "prefixItems": [
                {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 9007199254740991
                },
                {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 9007199254740991
                }
              ],
              "items": false,
              "minItems": 2,
              "maxItems": 2
            },
            "delay": {
              "type": "number",
              "minimum": 0
            }
          },
          "required": [
            "from",
            "tone"
          ],
          "additionalProperties": false
        },
        "clusters": {
          "description": "Group halos. Absent: show.",
          "type": "string",
          "enum": [
            "show",
            "dim",
            "hide"
          ]
        },
        "minimap": {
          "description": "Absent: shown when zoomed in.",
          "type": "boolean"
        }
      },
      "additionalProperties": false,
      "description": "What the map shows in one step. Every step states its whole map; transitions animate."
    },
    "LevelLists": {
      "type": "object",
      "properties": {
        "base": {
          "description": "The level of every element not listed. Absent: normal.",
          "$ref": "#/$defs/Level"
        },
        "hidden": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/Id"
          }
        },
        "ghost": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/Id"
          }
        },
        "dim": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/Id"
          }
        },
        "normal": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/Id"
          }
        },
        "focus": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/Id"
          }
        },
        "active": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/Id"
          }
        }
      },
      "additionalProperties": false,
      "description": "Emphasis levels, from hidden to active. An id listed at two levels takes the higher one."
    },
    "Level": {
      "type": "string",
      "enum": [
        "hidden",
        "ghost",
        "dim",
        "normal",
        "focus",
        "active"
      ]
    },
    "Trend": {
      "type": "string",
      "enum": [
        "up",
        "down",
        "mixed"
      ]
    },
    "Overlay": {
      "oneOf": [
        {
          "type": "object",
          "properties": {
            "type": {
              "type": "string",
              "const": "caption"
            }
          },
          "required": [
            "type"
          ],
          "additionalProperties": false,
          "description": "The scene's caption band: its title and headline."
        },
        {
          "type": "object",
          "properties": {
            "type": {
              "type": "string",
              "const": "title"
            },
            "title": {
              "type": "string",
              "minLength": 1
            },
            "subtitle": {
              "type": "string",
              "minLength": 1
            },
            "lines": {
              "description": "Further lines, e.g. author and course.",
              "type": "array",
              "items": {
                "type": "string",
                "minLength": 1
              }
            }
          },
          "required": [
            "type",
            "title"
          ],
          "additionalProperties": false,
          "description": "A title page over the map."
        },
        {
          "type": "object",
          "properties": {
            "type": {
              "type": "string",
              "const": "statement"
            },
            "lines": {
              "minItems": 1,
              "type": "array",
              "items": {
                "type": "string",
                "minLength": 1
              }
            },
            "kicker": {
              "description": "A small line above, e.g. \"INTERVENTION A\".",
              "type": "string",
              "minLength": 1
            },
            "style": {
              "type": "string",
              "enum": [
                "hero",
                "question",
                "closing",
                "title",
                "note"
              ]
            },
            "place": {
              "description": "Where on the stage. Absent: the default for the overlay type.",
              "type": "string",
              "enum": [
                "top-left",
                "top",
                "top-right",
                "left",
                "center",
                "right",
                "bottom-left",
                "bottom",
                "bottom-right"
              ]
            },
            "citations": {
              "type": "array",
              "items": {
                "$ref": "#/$defs/Citation"
              }
            }
          },
          "required": [
            "type",
            "lines"
          ],
          "additionalProperties": false,
          "description": "Large type over the stage: a question, a closing line, a short title or a caveat."
        },
        {
          "type": "object",
          "properties": {
            "type": {
              "type": "string",
              "const": "cards"
            },
            "title": {
              "type": "string",
              "minLength": 1
            },
            "items": {
              "minItems": 1,
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string",
                    "minLength": 1
                  },
                  "sub": {
                    "type": "string",
                    "minLength": 1
                  },
                  "icon": {
                    "description": "A small icon in place of the card’s number.",
                    "$ref": "#/$defs/CardIcon"
                  }
                },
                "required": [
                  "title"
                ],
                "additionalProperties": false
              }
            },
            "active": {
              "description": "Highlighted cards, from 0; the rest recede.",
              "type": "array",
              "items": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              }
            },
            "place": {
              "description": "Where on the stage. Absent: the default for the overlay type.",
              "type": "string",
              "enum": [
                "top-left",
                "top",
                "top-right",
                "left",
                "center",
                "right",
                "bottom-left",
                "bottom",
                "bottom-right"
              ]
            },
            "citations": {
              "type": "array",
              "items": {
                "$ref": "#/$defs/Citation"
              }
            }
          },
          "required": [
            "type",
            "items"
          ],
          "additionalProperties": false,
          "description": "Cards: a column beside the map, or a compact grid above or below it."
        },
        {
          "type": "object",
          "properties": {
            "type": {
              "type": "string",
              "const": "legend"
            },
            "place": {
              "description": "Where on the stage. Absent: the default for the overlay type.",
              "type": "string",
              "enum": [
                "top-left",
                "top",
                "top-right",
                "left",
                "center",
                "right",
                "bottom-left",
                "bottom",
                "bottom-right"
              ]
            },
            "citations": {
              "type": "array",
              "items": {
                "$ref": "#/$defs/Citation"
              }
            }
          },
          "required": [
            "type"
          ],
          "additionalProperties": false,
          "description": "A legend of the project's roles and link styles."
        },
        {
          "type": "object",
          "properties": {
            "type": {
              "type": "string",
              "const": "image"
            },
            "asset": {
              "description": "The id of an image in `assets`.",
              "$ref": "#/$defs/Id"
            },
            "alt": {
              "type": "string",
              "minLength": 1,
              "description": "What the image shows, for people who cannot see it."
            },
            "caption": {
              "type": "string",
              "minLength": 1
            },
            "credit": {
              "type": "string",
              "minLength": 1
            },
            "place": {
              "description": "Where on the stage. Absent: the default for the overlay type.",
              "type": "string",
              "enum": [
                "top-left",
                "top",
                "top-right",
                "left",
                "center",
                "right",
                "bottom-left",
                "bottom",
                "bottom-right"
              ]
            },
            "citations": {
              "type": "array",
              "items": {
                "$ref": "#/$defs/Citation"
              }
            }
          },
          "required": [
            "type",
            "asset",
            "alt"
          ],
          "additionalProperties": false,
          "description": "An embedded image."
        }
      ],
      "description": "Something drawn over the map. Overlays draw in list order, later ones on top."
    },
    "CardIcon": {
      "type": "string",
      "enum": [
        "circle",
        "ring",
        "square",
        "rounded-square",
        "octagon",
        "diamond",
        "triangle",
        "hexagon",
        "increases",
        "decreases",
        "conditional",
        "feedback",
        "reinforcing",
        "balancing",
        "common-stress",
        "common-trigger",
        "domino",
        "broken-link",
        "root"
      ],
      "description": "A small built-in icon: a role shape, a link or loop mark, or a way crises become entangled."
    }
  }
}
