{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://docs.kapiworks.com/schemas/content-2.json",
  "title": "Kapi Works content (content.json)",
  "description": "content.json: a course's lessons, pages and blocks, in reading order. Every text is a text ID from the words file.",
  "type": "object",
  "required": [
    "schema",
    "lessons"
  ],
  "additionalProperties": false,
  "properties": {
    "$schema": {
      "description": "This file's schema, https://docs.kapiworks.com/schemas/content-2.json, so editors check the file as you type. Kapi ignores it."
    },
    "schema": {
      "const": "kapi.works.content/2",
      "description": "The format, kapi.works.content/2."
    },
    "lessons": {
      "type": "array",
      "minItems": 1,
      "maxItems": 64,
      "items": {
        "$ref": "#/$defs/lesson"
      },
      "description": "The lessons, in order: up to 64 lessons and 200 pages in all."
    }
  },
  "$defs": {
    "lesson": {
      "description": "A lesson: a few pages on one topic.",
      "type": "object",
      "required": [
        "id",
        "title",
        "pages"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "id": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
          "description": "The lesson's ID. Its text IDs start with it, such as first-week.title. Lesson and page IDs are all different."
        },
        "title": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of the lesson's title."
        },
        "minutes": {
          "type": "integer",
          "minimum": 1,
          "maximum": 120,
          "description": "About how many minutes the lesson takes, from 1 to 120."
        },
        "requiredFor": {
          "type": [
            "array",
            "null"
          ],
          "minItems": 1,
          "items": {
            "type": "string",
            "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
            "description": "A role ID from kapi-works.json."
          },
          "description": "The role IDs the lesson is required for. null or left out: it's for everyone."
        },
        "pages": {
          "type": "array",
          "minItems": 1,
          "maxItems": 200,
          "items": {
            "$ref": "#/$defs/page"
          },
          "description": "The lesson's pages, in order."
        }
      }
    },
    "page": {
      "description": "A page: what a person sees at one time.",
      "type": "object",
      "required": [
        "id",
        "title"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "id": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,47}$",
          "description": "The page's ID: lower-case letters, digits and hyphens, up to 48 characters. Its text IDs start with it."
        },
        "title": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of the page's title."
        },
        "blocks": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/block"
          },
          "description": "What the page shows, from top to bottom."
        }
      }
    },
    "block": {
      "description": "A block on a page. Its type sets the keys it takes.",
      "type": "object",
      "required": [
        "type"
      ],
      "properties": {
        "type": {
          "enum": [
            "heading",
            "text",
            "list",
            "steps",
            "callout",
            "table",
            "quote",
            "image",
            "audio",
            "video",
            "film",
            "question",
            "contact",
            "sources",
            "divider"
          ],
          "description": "The kind of block."
        }
      },
      "allOf": [
        {
          "if": {
            "required": [
              "type"
            ],
            "properties": {
              "type": {
                "const": "heading"
              }
            }
          },
          "then": {
            "$ref": "#/$defs/block-heading"
          }
        },
        {
          "if": {
            "required": [
              "type"
            ],
            "properties": {
              "type": {
                "const": "text"
              }
            }
          },
          "then": {
            "$ref": "#/$defs/block-text"
          }
        },
        {
          "if": {
            "required": [
              "type"
            ],
            "properties": {
              "type": {
                "const": "list"
              }
            }
          },
          "then": {
            "$ref": "#/$defs/block-list"
          }
        },
        {
          "if": {
            "required": [
              "type"
            ],
            "properties": {
              "type": {
                "const": "steps"
              }
            }
          },
          "then": {
            "$ref": "#/$defs/block-steps"
          }
        },
        {
          "if": {
            "required": [
              "type"
            ],
            "properties": {
              "type": {
                "const": "callout"
              }
            }
          },
          "then": {
            "$ref": "#/$defs/block-callout"
          }
        },
        {
          "if": {
            "required": [
              "type"
            ],
            "properties": {
              "type": {
                "const": "table"
              }
            }
          },
          "then": {
            "$ref": "#/$defs/block-table"
          }
        },
        {
          "if": {
            "required": [
              "type"
            ],
            "properties": {
              "type": {
                "const": "quote"
              }
            }
          },
          "then": {
            "$ref": "#/$defs/block-quote"
          }
        },
        {
          "if": {
            "required": [
              "type"
            ],
            "properties": {
              "type": {
                "const": "image"
              }
            }
          },
          "then": {
            "$ref": "#/$defs/block-image"
          }
        },
        {
          "if": {
            "required": [
              "type"
            ],
            "properties": {
              "type": {
                "const": "audio"
              }
            }
          },
          "then": {
            "$ref": "#/$defs/block-audio"
          }
        },
        {
          "if": {
            "required": [
              "type"
            ],
            "properties": {
              "type": {
                "const": "video"
              }
            }
          },
          "then": {
            "$ref": "#/$defs/block-video"
          }
        },
        {
          "if": {
            "required": [
              "type"
            ],
            "properties": {
              "type": {
                "const": "film"
              }
            }
          },
          "then": {
            "$ref": "#/$defs/block-film"
          }
        },
        {
          "if": {
            "required": [
              "type"
            ],
            "properties": {
              "type": {
                "const": "question"
              }
            }
          },
          "then": {
            "$ref": "#/$defs/block-question"
          }
        },
        {
          "if": {
            "required": [
              "type"
            ],
            "properties": {
              "type": {
                "const": "contact"
              }
            }
          },
          "then": {
            "$ref": "#/$defs/block-contact"
          }
        },
        {
          "if": {
            "required": [
              "type"
            ],
            "properties": {
              "type": {
                "const": "sources"
              }
            }
          },
          "then": {
            "$ref": "#/$defs/block-sources"
          }
        },
        {
          "if": {
            "required": [
              "type"
            ],
            "properties": {
              "type": {
                "const": "divider"
              }
            }
          },
          "then": {
            "$ref": "#/$defs/block-divider"
          }
        }
      ]
    },
    "block-heading": {
      "description": "A heading.",
      "type": "object",
      "required": [
        "type",
        "text"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "type": {
          "const": "heading",
          "description": "The block's type."
        },
        "text": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of the heading."
        }
      }
    },
    "block-text": {
      "description": "A paragraph.",
      "type": "object",
      "required": [
        "type",
        "text"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "type": {
          "const": "text",
          "description": "The block's type."
        },
        "text": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of the paragraph."
        },
        "style": {
          "enum": [
            "body",
            "lead",
            "small"
          ],
          "description": "lead for an opening paragraph, small for a note, or body. Left out: body."
        }
      }
    },
    "block-list": {
      "description": "A list.",
      "type": "object",
      "required": [
        "type",
        "items"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "type": {
          "const": "list",
          "description": "The block's type."
        },
        "items": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "string",
            "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
            "description": "The text ID of an item."
          },
          "description": "The items, in order."
        },
        "ordered": {
          "type": "boolean",
          "description": "true to number the items."
        }
      }
    },
    "block-steps": {
      "description": "Numbered steps, such as how to use a tool.",
      "type": "object",
      "required": [
        "type",
        "items"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "type": {
          "const": "steps",
          "description": "The block's type."
        },
        "items": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "string",
            "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
            "description": "The text ID of a step."
          },
          "description": "The steps, in order."
        }
      }
    },
    "block-callout": {
      "description": "A note that stands out from the page.",
      "type": "object",
      "required": [
        "type",
        "tone",
        "text"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "type": {
          "const": "callout",
          "description": "The block's type."
        },
        "tone": {
          "enum": [
            "note",
            "important",
            "warning"
          ],
          "description": "How strongly it stands out: note, important or warning."
        },
        "text": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of the note."
        }
      }
    },
    "block-table": {
      "description": "A table.",
      "type": "object",
      "required": [
        "type",
        "rows"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "type": {
          "const": "table",
          "description": "The block's type."
        },
        "header": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "string",
            "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
            "description": "The text ID of a heading."
          },
          "description": "The column headings."
        },
        "rows": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "string",
              "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
              "description": "The text ID of a cell."
            },
            "description": "A row: the text ID of each cell."
          },
          "description": "The rows."
        }
      }
    },
    "block-quote": {
      "description": "A quotation.",
      "type": "object",
      "required": [
        "type",
        "text"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "type": {
          "const": "quote",
          "description": "The block's type."
        },
        "text": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of the quoted words."
        },
        "cite": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of who or what it quotes."
        }
      }
    },
    "block-image": {
      "description": "A picture.",
      "type": "object",
      "required": [
        "type",
        "media"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "type": {
          "const": "image",
          "description": "The block's type."
        },
        "media": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
          "description": "The media ID of a picture in kapi-works.json."
        }
      }
    },
    "block-audio": {
      "description": "A sound.",
      "type": "object",
      "required": [
        "type",
        "media"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "type": {
          "const": "audio",
          "description": "The block's type."
        },
        "media": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
          "description": "The media ID of a sound in kapi-works.json."
        }
      }
    },
    "block-video": {
      "description": "A video.",
      "type": "object",
      "required": [
        "type",
        "media"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "type": {
          "const": "video",
          "description": "The block's type."
        },
        "media": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
          "description": "The media ID of a video in kapi-works.json."
        }
      }
    },
    "block-film": {
      "description": "A film Kapi draws and plays from its scenes, with captions, a transcript and questions that pause it. A package with a film says \"sdk\": \"2.1\".",
      "type": "object",
      "required": [
        "type",
        "scenes"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "type": {
          "const": "film",
          "description": "The block's type."
        },
        "scenes": {
          "type": "array",
          "minItems": 2,
          "maxItems": 30,
          "items": {
            "$ref": "#/$defs/film-scene"
          },
          "description": "The scenes, in order: 2 to 30, up to 10 minutes in all. Scene IDs are all different."
        },
        "emails": {
          "type": "array",
          "maxItems": 4,
          "items": {
            "$ref": "#/$defs/film-email"
          },
          "description": "The emails the film's email scenes show, with IDs all different: up to 4."
        },
        "style": {
          "type": "string",
          "maxLength": 32000,
          "description": "CSS for the film's drawing scenes, up to 32000 bytes. It may use @keyframes, @media and @supports rules, and no addresses: url() names only a part of the same drawing. Kapi's clock plays every animation it starts, and every part sizes with box-sizing: border-box. The canvas is .kw-canvas; while a scene waits for a tap it also has .kw-targeting, and a tapped target has .kw-right or .kw-wrong. A part chosen for a question with several right parts has .kw-chosen, and every chosen part has .kw-right once the answer is right."
        }
      }
    },
    "block-question": {
      "description": "A practice question, with feedback after each answer.",
      "type": "object",
      "required": [
        "type",
        "question"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "type": {
          "const": "question",
          "description": "The block's type."
        },
        "question": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
          "description": "The ID of a question in questions.json that isn't in a test's pool."
        }
      }
    },
    "block-contact": {
      "description": "A company contact. Where the company hasn't filled it in, the page shows common.contact-missing.<key>.",
      "type": "object",
      "required": [
        "type",
        "contact"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "type": {
          "const": "contact",
          "description": "The block's type."
        },
        "contact": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
          "description": "The contact's key, such as it-help, declared in context as contacts.it-help."
        }
      }
    },
    "block-sources": {
      "description": "The titles of the documents the package was made from.",
      "type": "object",
      "required": [
        "type"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "type": {
          "const": "sources",
          "description": "The block's type."
        }
      }
    },
    "block-divider": {
      "description": "A line between parts of a page.",
      "type": "object",
      "required": [
        "type"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "type": {
          "const": "divider",
          "description": "The block's type."
        }
      }
    },
    "film-email": {
      "description": "An email a film draws once. Its email scenes point at it by ID.",
      "type": "object",
      "required": [
        "id",
        "from",
        "address",
        "subject",
        "lines"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "id": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
          "description": "The email's ID, which email scenes name."
        },
        "from": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of the sender's name."
        },
        "address": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of the sender's address. Use an address that ends in .example."
        },
        "subject": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of the subject."
        },
        "lines": {
          "type": "array",
          "minItems": 1,
          "maxItems": 4,
          "items": {
            "type": "string",
            "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
            "description": "The text ID of a line of the email."
          },
          "description": "The email's lines."
        },
        "button": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of a button in the email."
        },
        "link": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of the address the button really goes to, shown when the pointer rests on it."
        },
        "attachment": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of an attached file's name."
        }
      }
    },
    "film-scene": {
      "description": "A scene of a film. Its layout sets the keys it takes.",
      "type": "object",
      "required": [
        "layout"
      ],
      "properties": {
        "layout": {
          "enum": [
            "title",
            "number",
            "email",
            "chat",
            "person",
            "icons",
            "checklist",
            "drawing"
          ],
          "description": "The drawn layout."
        }
      },
      "allOf": [
        {
          "if": {
            "required": [
              "layout"
            ],
            "properties": {
              "layout": {
                "const": "title"
              }
            }
          },
          "then": {
            "$ref": "#/$defs/film-scene-title"
          }
        },
        {
          "if": {
            "required": [
              "layout"
            ],
            "properties": {
              "layout": {
                "const": "number"
              }
            }
          },
          "then": {
            "$ref": "#/$defs/film-scene-number"
          }
        },
        {
          "if": {
            "required": [
              "layout"
            ],
            "properties": {
              "layout": {
                "const": "email"
              }
            }
          },
          "then": {
            "$ref": "#/$defs/film-scene-email"
          }
        },
        {
          "if": {
            "required": [
              "layout"
            ],
            "properties": {
              "layout": {
                "const": "chat"
              }
            }
          },
          "then": {
            "$ref": "#/$defs/film-scene-chat"
          }
        },
        {
          "if": {
            "required": [
              "layout"
            ],
            "properties": {
              "layout": {
                "const": "person"
              }
            }
          },
          "then": {
            "$ref": "#/$defs/film-scene-person"
          }
        },
        {
          "if": {
            "required": [
              "layout"
            ],
            "properties": {
              "layout": {
                "const": "icons"
              }
            }
          },
          "then": {
            "$ref": "#/$defs/film-scene-icons"
          }
        },
        {
          "if": {
            "required": [
              "layout"
            ],
            "properties": {
              "layout": {
                "const": "checklist"
              }
            }
          },
          "then": {
            "$ref": "#/$defs/film-scene-checklist"
          }
        },
        {
          "if": {
            "required": [
              "layout"
            ],
            "properties": {
              "layout": {
                "const": "drawing"
              }
            }
          },
          "then": {
            "$ref": "#/$defs/film-scene-drawing"
          }
        }
      ]
    },
    "film-scene-title": {
      "description": "A title card.",
      "type": "object",
      "required": [
        "id",
        "title",
        "seconds",
        "layout",
        "caption",
        "picture",
        "heading"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "id": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
          "description": "The scene's ID."
        },
        "title": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of the scene's name, shown in the transcript and on the seek bar."
        },
        "seconds": {
          "type": "number",
          "minimum": 4,
          "maximum": 60,
          "description": "How long the scene runs, from 4 to 60 seconds, in tenths at most, such as 6.5."
        },
        "layout": {
          "const": "title",
          "description": "The drawn layout."
        },
        "caption": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of what the narrator says, shown as captions. Short sentences, and never a placeholder."
        },
        "picture": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of what the scene shows, for the transcript and screen readers."
        },
        "headline": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of a few words on the picture."
        },
        "pause": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
          "description": "The ID of a choice or multi practice question the film stops for after this scene. No page or test pool may hold it too."
        },
        "heading": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of the title."
        },
        "kicker": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of a small line above it."
        }
      }
    },
    "film-scene-number": {
      "description": "A number that counts up, with its label.",
      "type": "object",
      "required": [
        "id",
        "title",
        "seconds",
        "layout",
        "caption",
        "picture",
        "number",
        "label"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "id": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
          "description": "The scene's ID."
        },
        "title": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of the scene's name, shown in the transcript and on the seek bar."
        },
        "seconds": {
          "type": "number",
          "minimum": 4,
          "maximum": 60,
          "description": "How long the scene runs, from 4 to 60 seconds, in tenths at most, such as 6.5."
        },
        "layout": {
          "const": "number",
          "description": "The drawn layout."
        },
        "caption": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of what the narrator says, shown as captions. Short sentences, and never a placeholder."
        },
        "picture": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of what the scene shows, for the transcript and screen readers."
        },
        "headline": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of a few words on the picture."
        },
        "pause": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
          "description": "The ID of a choice or multi practice question the film stops for after this scene. No page or test pool may hold it too."
        },
        "number": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of the number, such as 38%."
        },
        "label": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of what the number counts."
        },
        "note": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of a small line under it, such as where the number comes from."
        }
      }
    },
    "film-scene-email": {
      "description": "An email from this film's emails. Each beat moves the camera to the part it marks, in order.",
      "type": "object",
      "required": [
        "id",
        "title",
        "seconds",
        "layout",
        "caption",
        "picture",
        "email"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "id": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
          "description": "The scene's ID."
        },
        "title": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of the scene's name, shown in the transcript and on the seek bar."
        },
        "seconds": {
          "type": "number",
          "minimum": 4,
          "maximum": 60,
          "description": "How long the scene runs, from 4 to 60 seconds, in tenths at most, such as 6.5."
        },
        "layout": {
          "const": "email",
          "description": "The drawn layout."
        },
        "caption": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of what the narrator says, shown as captions. Short sentences, and never a placeholder."
        },
        "picture": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of what the scene shows, for the transcript and screen readers."
        },
        "headline": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of a few words on the picture."
        },
        "pause": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
          "description": "The ID of a choice or multi practice question the film stops for after this scene. No page or test pool may hold it too."
        },
        "email": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
          "description": "The ID of an email in this film's emails."
        },
        "beats": {
          "type": "array",
          "uniqueItems": true,
          "items": {
            "enum": [
              "arrive",
              "mark-from",
              "reveal-address",
              "mark-subject",
              "mark-lines",
              "point-link",
              "mark-attachment",
              "report"
            ]
          },
          "description": "What happens, in order. Left out: the email shows still."
        }
      }
    },
    "film-scene-chat": {
      "description": "Chat messages that arrive one by one.",
      "type": "object",
      "required": [
        "id",
        "title",
        "seconds",
        "layout",
        "caption",
        "picture",
        "messages"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "id": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
          "description": "The scene's ID."
        },
        "title": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of the scene's name, shown in the transcript and on the seek bar."
        },
        "seconds": {
          "type": "number",
          "minimum": 4,
          "maximum": 60,
          "description": "How long the scene runs, from 4 to 60 seconds, in tenths at most, such as 6.5."
        },
        "layout": {
          "const": "chat",
          "description": "The drawn layout."
        },
        "caption": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of what the narrator says, shown as captions. Short sentences, and never a placeholder."
        },
        "picture": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of what the scene shows, for the transcript and screen readers."
        },
        "headline": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of a few words on the picture."
        },
        "pause": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
          "description": "The ID of a choice or multi practice question the film stops for after this scene. No page or test pool may hold it too."
        },
        "messages": {
          "type": "array",
          "minItems": 1,
          "maxItems": 5,
          "items": {
            "description": "A message.",
            "type": "object",
            "required": [
              "who",
              "text"
            ],
            "additionalProperties": false,
            "properties": {
              "$schema": {
                "description": "A schema address for editors. Kapi ignores it."
              },
              "who": {
                "type": "string",
                "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
                "description": "The text ID of who sends it."
              },
              "text": {
                "type": "string",
                "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
                "description": "The text ID of the message."
              }
            }
          },
          "description": "The messages, in order."
        },
        "mark": {
          "type": "integer",
          "minimum": 1,
          "maximum": 5,
          "description": "The number of the message to mark as the one to notice, counting from 1."
        }
      }
    },
    "film-scene-person": {
      "description": "A drawn person at work, on a call with someone else or saying something.",
      "type": "object",
      "required": [
        "id",
        "title",
        "seconds",
        "layout",
        "caption",
        "picture",
        "label",
        "prop"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "id": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
          "description": "The scene's ID."
        },
        "title": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of the scene's name, shown in the transcript and on the seek bar."
        },
        "seconds": {
          "type": "number",
          "minimum": 4,
          "maximum": 60,
          "description": "How long the scene runs, from 4 to 60 seconds, in tenths at most, such as 6.5."
        },
        "layout": {
          "const": "person",
          "description": "The drawn layout."
        },
        "caption": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of what the narrator says, shown as captions. Short sentences, and never a placeholder."
        },
        "picture": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of what the scene shows, for the transcript and screen readers."
        },
        "headline": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of a few words on the picture."
        },
        "pause": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
          "description": "The ID of a choice or multi practice question the film stops for after this scene. No page or test pool may hold it too."
        },
        "label": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of who it is, such as You."
        },
        "prop": {
          "enum": [
            "phone",
            "laptop",
            "none"
          ],
          "description": "What they hold: a phone, a laptop, or none."
        },
        "with": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of who they're talking to, which draws a call."
        },
        "says": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of what they say, in a speech bubble."
        }
      }
    },
    "film-scene-icons": {
      "description": "Icons with their words, each in turn.",
      "type": "object",
      "required": [
        "id",
        "title",
        "seconds",
        "layout",
        "caption",
        "picture",
        "items"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "id": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
          "description": "The scene's ID."
        },
        "title": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of the scene's name, shown in the transcript and on the seek bar."
        },
        "seconds": {
          "type": "number",
          "minimum": 4,
          "maximum": 60,
          "description": "How long the scene runs, from 4 to 60 seconds, in tenths at most, such as 6.5."
        },
        "layout": {
          "const": "icons",
          "description": "The drawn layout."
        },
        "caption": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of what the narrator says, shown as captions. Short sentences, and never a placeholder."
        },
        "picture": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of what the scene shows, for the transcript and screen readers."
        },
        "headline": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of a few words on the picture."
        },
        "pause": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
          "description": "The ID of a choice or multi practice question the film stops for after this scene. No page or test pool may hold it too."
        },
        "items": {
          "type": "array",
          "minItems": 2,
          "maxItems": 4,
          "items": {
            "description": "An icon and its words.",
            "type": "object",
            "required": [
              "icon",
              "text"
            ],
            "additionalProperties": false,
            "properties": {
              "$schema": {
                "description": "A schema address for editors. Kapi ignores it."
              },
              "icon": {
                "enum": [
                  "search",
                  "phone",
                  "flag",
                  "mail",
                  "link",
                  "file",
                  "lock",
                  "key",
                  "shield",
                  "alert",
                  "check",
                  "clock",
                  "card",
                  "person"
                ],
                "description": "The icon."
              },
              "text": {
                "type": "string",
                "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
                "description": "The text ID of its words."
              }
            }
          },
          "description": "The icons, in order."
        }
      }
    },
    "film-scene-checklist": {
      "description": "A checklist that ticks each item in turn.",
      "type": "object",
      "required": [
        "id",
        "title",
        "seconds",
        "layout",
        "caption",
        "picture",
        "items"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "id": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
          "description": "The scene's ID."
        },
        "title": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of the scene's name, shown in the transcript and on the seek bar."
        },
        "seconds": {
          "type": "number",
          "minimum": 4,
          "maximum": 60,
          "description": "How long the scene runs, from 4 to 60 seconds, in tenths at most, such as 6.5."
        },
        "layout": {
          "const": "checklist",
          "description": "The drawn layout."
        },
        "caption": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of what the narrator says, shown as captions. Short sentences, and never a placeholder."
        },
        "picture": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of what the scene shows, for the transcript and screen readers."
        },
        "headline": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of a few words on the picture."
        },
        "pause": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
          "description": "The ID of a choice or multi practice question the film stops for after this scene. No page or test pool may hold it too."
        },
        "items": {
          "type": "array",
          "minItems": 2,
          "maxItems": 5,
          "items": {
            "type": "string",
            "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
            "description": "The text ID of an item."
          },
          "description": "The items, in order."
        }
      }
    },
    "film-scene-drawing": {
      "description": "A scene the author draws on a 960 by 540 canvas, with the film's style.",
      "type": "object",
      "required": [
        "id",
        "title",
        "seconds",
        "layout",
        "caption",
        "picture",
        "drawing"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "id": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
          "description": "The scene's ID."
        },
        "title": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of the scene's name, shown in the transcript and on the seek bar."
        },
        "seconds": {
          "type": "number",
          "minimum": 4,
          "maximum": 60,
          "description": "How long the scene runs, from 4 to 60 seconds, in tenths at most, such as 6.5."
        },
        "layout": {
          "const": "drawing",
          "description": "The drawn layout."
        },
        "caption": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of what the narrator says, shown as captions. Short sentences, and never a placeholder."
        },
        "picture": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of what the scene shows, for the transcript and screen readers."
        },
        "headline": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of a few words on the picture."
        },
        "pause": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
          "description": "The ID of a choice or multi practice question the film stops for after this scene. No page or test pool may hold it too."
        },
        "drawing": {
          "type": "string",
          "maxLength": 16000,
          "pattern": "^(?:\\s|&nbsp;|<[^<>]*>)*$",
          "description": "The scene's markup, up to 16000 bytes: div, span, p, b, strong, i, em, u, s, small, sup, sub, br, ul, ol, li, and inside an svg svg, g, path, rect, circle, ellipse, line, polyline, polygon, defs, linearGradient, radialGradient, stop, clipPath, with only class, style, id, aria-hidden, data-text, data-count, data-from, data-d, data-du, data-target, viewBox, width, height, x, y, x1, y1, x2, y2, cx, cy, r, rx, ry, fx, fy, d, points, fill, fill-opacity, fill-rule, clip-rule, clip-path, stroke, stroke-width, stroke-linejoin, stroke-linecap, stroke-dasharray, stroke-opacity, opacity, offset, stop-color, stop-opacity, transform, gradientUnits, gradientTransform, preserveAspectRatio. Every word is a text ID on an empty element, such as <span data-text=\"intro.name\"></span>. data-count counts a number up from data-from, after data-d seconds over data-du seconds. data-target makes a part a choice of the question the scene stops for, tapped on the picture: a choice question takes one tap, and a multi question takes every right part, then Check answer."
        }
      }
    }
  }
}
