{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://docs.zvid.io/schemas/render-payload.schema.json",
  "title": "Zvid authoring project and template input",
  "description": "The payload accepted by render requests and stored templates, before variable substitution, conditions and scene iteration. Whole placeholders preserve the variable's type. Run authenticated validation to resolve values, enforce semantic rules and apply your account limits. Stored video templates require explicit positive scene durations.",
  "type": "object",
  "properties": {
    "type": {
      "enum": [
        "video",
        "image"
      ],
      "default": "video",
      "description": "Render type. \"image\" produces a still (png/jpg/webp) and forbids all time-domain fields."
    },
    "name": {
      "anyOf": [
        {
          "type": "string",
          "pattern": "^[a-zA-Z0-9_\\- ]+$",
          "maxLength": 1000,
          "default": "unnamed",
          "description": "Output name. Letters, digits, space, _ and - ONLY (no punctuation)."
        },
        {
          "type": "string",
          "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
        }
      ]
    },
    "resolution": {
      "anyOf": [
        {
          "enum": [
            "sd",
            "hd",
            "full-hd",
            "squared",
            "youtube-short",
            "youtube-video",
            "tiktok",
            "instagram-reel",
            "instagram-post",
            "instagram-story",
            "instagram-feed",
            "twitter-landscape",
            "twitter-portrait",
            "twitter-square",
            "facebook-video",
            "facebook-story",
            "facebook-post",
            "snapchat",
            "custom"
          ],
          "description": "Canvas preset. Any value except \"custom\" OVERRIDES width/height."
        },
        {
          "type": "string",
          "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
        }
      ]
    },
    "width": {
      "anyOf": [
        {
          "type": "integer",
          "minimum": 1,
          "description": "Canvas width in px (plan-limited).",
          "default": 1280,
          "x-zvid-plan-dependent": true
        },
        {
          "type": "string",
          "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
        }
      ]
    },
    "height": {
      "anyOf": [
        {
          "type": "integer",
          "minimum": 1,
          "description": "Canvas height in px (plan-limited).",
          "default": 720,
          "x-zvid-plan-dependent": true
        },
        {
          "type": "string",
          "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
        }
      ]
    },
    "duration": {
      "anyOf": [
        {
          "type": "number",
          "minimum": 0.1,
          "description": "Video length in seconds (plan-limited). With durationMode auto this is an optional minimum. Scene projects always include the full scene sequence.",
          "default": 10,
          "x-zvid-plan-dependent": true
        },
        {
          "type": "string",
          "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
        }
      ]
    },
    "durationMode": {
      "anyOf": [
        {
          "enum": [
            "auto",
            "fixed"
          ],
          "description": "Auto derives length from scenes, explicit element ends and captions. Supply videoEnd/exitEnd and audioEnd/exit for API submission. Omit to preserve legacy timing."
        },
        {
          "type": "string",
          "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
        }
      ]
    },
    "frameRate": {
      "anyOf": [
        {
          "type": "integer",
          "minimum": 1,
          "maximum": 60,
          "description": "Frames per second.",
          "default": 30
        },
        {
          "type": "string",
          "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
        }
      ]
    },
    "outputFormat": {
      "anyOf": [
        {
          "type": "string",
          "enum": [
            "mp4",
            "mov",
            "avi",
            "webm",
            "png",
            "jpg",
            "jpeg",
            "webp"
          ],
          "description": "Videos: mp4 (default), mov, avi, webm. Images (type: \"image\"): png (default), jpg, jpeg, webp."
        },
        {
          "type": "string",
          "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
        }
      ]
    },
    "backgroundColor": {
      "anyOf": [
        {
          "type": "string",
          "pattern": "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$",
          "description": "Canvas background color.",
          "default": "#ffffff"
        },
        {
          "type": "string",
          "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
        }
      ]
    },
    "snapshotTime": {
      "anyOf": [
        {
          "type": "number",
          "minimum": 0,
          "maximum": 3600,
          "description": "Image renders only: second of the (virtual) timeline to snapshot."
        },
        {
          "type": "string",
          "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
        }
      ]
    },
    "quality": {
      "anyOf": [
        {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "description": "Image renders only: jpg/webp quality (NOT valid for png)."
        },
        {
          "type": "string",
          "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
        }
      ]
    },
    "transparent": {
      "anyOf": [
        {
          "type": "boolean",
          "description": "Image renders only: transparent background (png/webp only, not jpg)."
        },
        {
          "type": "string",
          "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
        }
      ]
    },
    "visuals": {
      "anyOf": [
        {
          "type": "array",
          "items": {
            "$ref": "#/$defs/visual"
          },
          "description": "Elements shown for the whole project (outside scenes)."
        },
        {
          "type": "string",
          "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
        }
      ]
    },
    "audios": {
      "anyOf": [
        {
          "type": "array",
          "items": {
            "$ref": "#/$defs/audioItem"
          },
          "description": "Project-level audio tracks (video only, plan-limited count)."
        },
        {
          "type": "string",
          "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
        }
      ]
    },
    "scenes": {
      "anyOf": [
        {
          "type": "array",
          "items": {
            "$ref": "#/$defs/scene"
          },
          "description": "Sequential timeline segments (video only, plan-limited count).",
          "x-zvid-plan-dependent": true
        },
        {
          "type": "string",
          "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
        }
      ]
    },
    "thumbnail": {
      "anyOf": [
        {
          "type": "string",
          "minLength": 1,
          "maxLength": 2048,
          "pattern": "^https?://",
          "description": "Custom thumbnail image URL (video only). Must be a PUBLIC http(s) URL: no credentials, no spaces/backslashes, port 80/443 only, no localhost/.local, no private IPs."
        },
        {
          "type": "string",
          "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
        }
      ]
    },
    "subtitle": {
      "anyOf": [
        {
          "$ref": "#/$defs/subtitle"
        },
        {
          "type": "string",
          "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
        }
      ]
    },
    "variables": {
      "type": "object",
      "additionalProperties": true,
      "description": "Default variable values; request variables override these. Scene defaults shadow project values inside that scene."
    }
  },
  "additionalProperties": false,
  "allOf": [
    {
      "if": {
        "properties": {
          "type": {
            "const": "image"
          }
        },
        "required": [
          "type"
        ]
      },
      "then": {
        "properties": {
          "duration": false,
          "durationMode": false,
          "frameRate": false,
          "audios": false,
          "scenes": false,
          "thumbnail": false,
          "subtitle": false,
          "outputFormat": {
            "anyOf": [
              {
                "enum": [
                  "png",
                  "jpg",
                  "jpeg",
                  "webp"
                ],
                "default": "png"
              },
              {
                "type": "string",
                "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
              }
            ]
          },
          "visuals": {
            "anyOf": [
              {
                "type": "array",
                "items": {
                  "oneOf": [
                    {
                      "$ref": "#/$defs/imageElement"
                    },
                    {
                      "$ref": "#/$defs/textElement"
                    },
                    {
                      "$ref": "#/$defs/svgElement"
                    }
                  ]
                }
              },
              {
                "type": "string",
                "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
              }
            ]
          }
        }
      },
      "else": {
        "properties": {
          "snapshotTime": false,
          "quality": false,
          "transparent": false,
          "outputFormat": {
            "anyOf": [
              {
                "enum": [
                  "mp4",
                  "mov",
                  "avi",
                  "webm"
                ],
                "default": "mp4"
              },
              {
                "type": "string",
                "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
              }
            ]
          }
        }
      }
    },
    {
      "if": {
        "properties": {
          "type": {
            "const": "image"
          },
          "outputFormat": {
            "enum": [
              "png"
            ]
          }
        },
        "required": [
          "type",
          "outputFormat"
        ]
      },
      "then": {
        "properties": {
          "quality": false
        }
      }
    },
    {
      "if": {
        "properties": {
          "type": {
            "const": "image"
          }
        },
        "required": [
          "type"
        ],
        "not": {
          "required": [
            "outputFormat"
          ]
        }
      },
      "then": {
        "properties": {
          "quality": false
        }
      }
    },
    {
      "if": {
        "properties": {
          "type": {
            "const": "image"
          },
          "outputFormat": {
            "enum": [
              "jpg",
              "jpeg"
            ]
          }
        },
        "required": [
          "type",
          "outputFormat"
        ]
      },
      "then": {
        "properties": {
          "transparent": {
            "anyOf": [
              {
                "const": false
              },
              {
                "type": "string",
                "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
              }
            ]
          }
        }
      }
    }
  ],
  "$defs": {
    "visual": {
      "title": "Visual element",
      "description": "One canvas element, discriminated by `type` (IMAGE | VIDEO | GIF | SVG | TEXT, case-insensitive).",
      "type": "object",
      "required": [
        "type"
      ],
      "oneOf": [
        {
          "$ref": "#/$defs/imageElement"
        },
        {
          "$ref": "#/$defs/videoElement"
        },
        {
          "$ref": "#/$defs/gifElement"
        },
        {
          "$ref": "#/$defs/svgElement"
        },
        {
          "$ref": "#/$defs/textElement"
        }
      ]
    },
    "imageElement": {
      "type": "object",
      "title": "IMAGE element",
      "description": "A raster image placed on the canvas.",
      "properties": {
        "type": {
          "type": "string",
          "pattern": "^[Ii][Mm][Aa][Gg][Ee]$",
          "description": "\"IMAGE\" (case-insensitive)."
        },
        "x": {
          "anyOf": [
            {
              "type": "number",
              "description": "Anchor-point X in canvas px (may be negative). ONLY used when position is \"custom\" — every other preset OVERWRITES x/y.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "y": {
          "anyOf": [
            {
              "type": "number",
              "description": "Anchor-point Y in canvas px (may be negative). ONLY used when position is \"custom\" — every other preset OVERWRITES x/y.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "width": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 1,
              "description": "Element width in px (plan-limited).",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "height": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 1,
              "description": "Element height in px (plan-limited).",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "position": {
          "anyOf": [
            {
              "enum": [
                "top-left",
                "top-center",
                "top-right",
                "center-left",
                "center-center",
                "center-right",
                "bottom-right",
                "bottom-center",
                "bottom-left",
                "custom"
              ],
              "description": "Placement preset. Sets BOTH the canvas point AND the default anchor (e.g. \"bottom-center\" puts the element's bottom edge flush with the frame bottom — no margin). x/y are IGNORED unless \"custom\". Two elements with the same preset render exactly stacked."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "anchor": {
          "anyOf": [
            {
              "enum": [
                "top-left",
                "top-center",
                "top-right",
                "center-left",
                "center-center",
                "center-right",
                "bottom-right",
                "bottom-center",
                "bottom-left",
                "custom"
              ],
              "description": "Which point of the ELEMENT sits at the position point. Defaults to the position preset itself (\"custom\" defaults to top-left)."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "resize": {
          "anyOf": [
            {
              "enum": [
                "contain",
                "cover"
              ],
              "description": "How media fits its frame: contain (letterbox) or cover (center-crop). With explicit width/height the media fits INTO that box (CSS object-fit); without a box it is sized against the full canvas."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "enterBegin": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "description": "Seconds when the element starts appearing (enter animation start).",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "enterEnd": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "description": "Seconds when the enter animation finishes. Must be >= enterBegin.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "exitBegin": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "description": "Seconds when the exit animation starts.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "exitEnd": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "description": "Seconds when the element is fully gone. Must be >= exitBegin.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "opacity": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "maximum": 1,
              "description": "Element opacity, 0..1."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "angle": {
          "anyOf": [
            {
              "type": "number",
              "minimum": -360,
              "maximum": 360,
              "description": "Rotation in degrees."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "flipV": {
          "anyOf": [
            {
              "type": "boolean",
              "description": "Flip vertically."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "flipH": {
          "anyOf": [
            {
              "type": "boolean",
              "description": "Flip horizontally."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "track": {
          "anyOf": [
            {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000000,
              "description": "Z-order track; higher tracks render on top."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "enterAnimation": {
          "anyOf": [
            {
              "anyOf": [
                {
                  "enum": [
                    "fade",
                    "fadeblack",
                    "fadewhite",
                    "distance",
                    "wipeleft",
                    "wiperight",
                    "wipeup",
                    "wipedown",
                    "slideleft",
                    "slideright",
                    "slideup",
                    "slidedown",
                    "smoothleft",
                    "smoothright",
                    "smoothup",
                    "smoothdown",
                    "circlecrop",
                    "rectcrop",
                    "circleclose",
                    "circleopen",
                    "horzclose",
                    "horzopen",
                    "vertclose",
                    "vertopen",
                    "diagbl",
                    "diagbr",
                    "diagtl",
                    "diagtr",
                    "hlslice",
                    "hrslice",
                    "vuslice",
                    "vdslice",
                    "dissolve",
                    "pixelize",
                    "radial",
                    "hblur",
                    "wipetl",
                    "wipetr",
                    "wipebl",
                    "wipebr",
                    "fadegrays",
                    "zoomin",
                    "hlwind",
                    "hrwind",
                    "squeezeh",
                    "squeezev",
                    "fadefast",
                    "fadeslow",
                    "vuwind",
                    "vdwind",
                    "coverleft",
                    "coverright",
                    "coverup",
                    "coverdown",
                    "revealleft",
                    "revealright",
                    "revealup",
                    "revealdown"
                  ]
                },
                {
                  "type": "null"
                }
              ],
              "description": "Enter animation effect (xfade family) or null."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "exitAnimation": {
          "anyOf": [
            {
              "anyOf": [
                {
                  "enum": [
                    "fade",
                    "fadeblack",
                    "fadewhite",
                    "distance",
                    "wipeleft",
                    "wiperight",
                    "wipeup",
                    "wipedown",
                    "slideleft",
                    "slideright",
                    "slideup",
                    "slidedown",
                    "smoothleft",
                    "smoothright",
                    "smoothup",
                    "smoothdown",
                    "circlecrop",
                    "rectcrop",
                    "circleclose",
                    "circleopen",
                    "horzclose",
                    "horzopen",
                    "vertclose",
                    "vertopen",
                    "diagbl",
                    "diagbr",
                    "diagtl",
                    "diagtr",
                    "hlslice",
                    "hrslice",
                    "vuslice",
                    "vdslice",
                    "dissolve",
                    "pixelize",
                    "radial",
                    "hblur",
                    "wipetl",
                    "wipetr",
                    "wipebl",
                    "wipebr",
                    "fadegrays",
                    "zoomin",
                    "hlwind",
                    "hrwind",
                    "squeezeh",
                    "squeezev",
                    "fadefast",
                    "fadeslow",
                    "vuwind",
                    "vdwind",
                    "coverleft",
                    "coverright",
                    "coverup",
                    "coverdown",
                    "revealleft",
                    "revealright",
                    "revealup",
                    "revealdown"
                  ]
                },
                {
                  "type": "null"
                }
              ],
              "description": "Exit animation effect (xfade family) or null."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "src": {
          "anyOf": [
            {
              "type": "string",
              "minLength": 1,
              "maxLength": 2048,
              "pattern": "^https?://",
              "description": "Source media URL. Must be a PUBLIC http(s) URL: no credentials, no spaces/backslashes, port 80/443 only, no localhost/.local, no private IPs."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "cropParams": {
          "anyOf": [
            {
              "$ref": "#/$defs/cropParams"
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "filter": {
          "anyOf": [
            {
              "$ref": "#/$defs/filter"
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "chromaKey": {
          "anyOf": [
            {
              "$ref": "#/$defs/chromaKey"
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "zoom": {
          "anyOf": [
            {
              "$ref": "#/$defs/zoom"
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "radius": {
          "anyOf": [
            {
              "$ref": "#/$defs/radius"
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "condition": {
          "description": "A literal flag or placeholder. false, 0, an empty string, null or an absent resolved value prune this item. Expressions are not supported."
        }
      },
      "required": [
        "type",
        "src"
      ],
      "additionalProperties": false
    },
    "videoElement": {
      "type": "object",
      "title": "VIDEO element",
      "description": "A video clip placed on the canvas (video projects only).",
      "properties": {
        "type": {
          "type": "string",
          "pattern": "^[Vv][Ii][Dd][Ee][Oo]$",
          "description": "\"VIDEO\" (case-insensitive)."
        },
        "x": {
          "anyOf": [
            {
              "type": "number",
              "description": "Anchor-point X in canvas px (may be negative). ONLY used when position is \"custom\" — every other preset OVERWRITES x/y.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "y": {
          "anyOf": [
            {
              "type": "number",
              "description": "Anchor-point Y in canvas px (may be negative). ONLY used when position is \"custom\" — every other preset OVERWRITES x/y.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "width": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 1,
              "description": "Element width in px (plan-limited).",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "height": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 1,
              "description": "Element height in px (plan-limited).",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "position": {
          "anyOf": [
            {
              "enum": [
                "top-left",
                "top-center",
                "top-right",
                "center-left",
                "center-center",
                "center-right",
                "bottom-right",
                "bottom-center",
                "bottom-left",
                "custom"
              ],
              "description": "Placement preset. Sets BOTH the canvas point AND the default anchor (e.g. \"bottom-center\" puts the element's bottom edge flush with the frame bottom — no margin). x/y are IGNORED unless \"custom\". Two elements with the same preset render exactly stacked."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "anchor": {
          "anyOf": [
            {
              "enum": [
                "top-left",
                "top-center",
                "top-right",
                "center-left",
                "center-center",
                "center-right",
                "bottom-right",
                "bottom-center",
                "bottom-left",
                "custom"
              ],
              "description": "Which point of the ELEMENT sits at the position point. Defaults to the position preset itself (\"custom\" defaults to top-left)."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "resize": {
          "anyOf": [
            {
              "enum": [
                "contain",
                "cover"
              ],
              "description": "How media fits its frame: contain (letterbox) or cover (center-crop). With explicit width/height the media fits INTO that box (CSS object-fit); without a box it is sized against the full canvas."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "enterBegin": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "description": "Seconds when the element starts appearing (enter animation start).",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "enterEnd": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "description": "Seconds when the enter animation finishes. Must be >= enterBegin.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "exitBegin": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "description": "Seconds when the exit animation starts.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "exitEnd": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "description": "Seconds when the element is fully gone. Must be >= exitBegin.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "opacity": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "maximum": 1,
              "description": "Element opacity, 0..1."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "angle": {
          "anyOf": [
            {
              "type": "number",
              "minimum": -360,
              "maximum": 360,
              "description": "Rotation in degrees."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "flipV": {
          "anyOf": [
            {
              "type": "boolean",
              "description": "Flip vertically."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "flipH": {
          "anyOf": [
            {
              "type": "boolean",
              "description": "Flip horizontally."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "track": {
          "anyOf": [
            {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000000,
              "description": "Z-order track; higher tracks render on top."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "enterAnimation": {
          "anyOf": [
            {
              "anyOf": [
                {
                  "enum": [
                    "fade",
                    "fadeblack",
                    "fadewhite",
                    "distance",
                    "wipeleft",
                    "wiperight",
                    "wipeup",
                    "wipedown",
                    "slideleft",
                    "slideright",
                    "slideup",
                    "slidedown",
                    "smoothleft",
                    "smoothright",
                    "smoothup",
                    "smoothdown",
                    "circlecrop",
                    "rectcrop",
                    "circleclose",
                    "circleopen",
                    "horzclose",
                    "horzopen",
                    "vertclose",
                    "vertopen",
                    "diagbl",
                    "diagbr",
                    "diagtl",
                    "diagtr",
                    "hlslice",
                    "hrslice",
                    "vuslice",
                    "vdslice",
                    "dissolve",
                    "pixelize",
                    "radial",
                    "hblur",
                    "wipetl",
                    "wipetr",
                    "wipebl",
                    "wipebr",
                    "fadegrays",
                    "zoomin",
                    "hlwind",
                    "hrwind",
                    "squeezeh",
                    "squeezev",
                    "fadefast",
                    "fadeslow",
                    "vuwind",
                    "vdwind",
                    "coverleft",
                    "coverright",
                    "coverup",
                    "coverdown",
                    "revealleft",
                    "revealright",
                    "revealup",
                    "revealdown"
                  ]
                },
                {
                  "type": "null"
                }
              ],
              "description": "Enter animation effect (xfade family) or null."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "exitAnimation": {
          "anyOf": [
            {
              "anyOf": [
                {
                  "enum": [
                    "fade",
                    "fadeblack",
                    "fadewhite",
                    "distance",
                    "wipeleft",
                    "wiperight",
                    "wipeup",
                    "wipedown",
                    "slideleft",
                    "slideright",
                    "slideup",
                    "slidedown",
                    "smoothleft",
                    "smoothright",
                    "smoothup",
                    "smoothdown",
                    "circlecrop",
                    "rectcrop",
                    "circleclose",
                    "circleopen",
                    "horzclose",
                    "horzopen",
                    "vertclose",
                    "vertopen",
                    "diagbl",
                    "diagbr",
                    "diagtl",
                    "diagtr",
                    "hlslice",
                    "hrslice",
                    "vuslice",
                    "vdslice",
                    "dissolve",
                    "pixelize",
                    "radial",
                    "hblur",
                    "wipetl",
                    "wipetr",
                    "wipebl",
                    "wipebr",
                    "fadegrays",
                    "zoomin",
                    "hlwind",
                    "hrwind",
                    "squeezeh",
                    "squeezev",
                    "fadefast",
                    "fadeslow",
                    "vuwind",
                    "vdwind",
                    "coverleft",
                    "coverright",
                    "coverup",
                    "coverdown",
                    "revealleft",
                    "revealright",
                    "revealup",
                    "revealdown"
                  ]
                },
                {
                  "type": "null"
                }
              ],
              "description": "Exit animation effect (xfade family) or null."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "src": {
          "anyOf": [
            {
              "type": "string",
              "minLength": 1,
              "maxLength": 2048,
              "pattern": "^https?://",
              "description": "Source media URL. Must be a PUBLIC http(s) URL: no credentials, no spaces/backslashes, port 80/443 only, no localhost/.local, no private IPs."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "cropParams": {
          "anyOf": [
            {
              "$ref": "#/$defs/cropParams"
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "filter": {
          "anyOf": [
            {
              "$ref": "#/$defs/filter"
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "chromaKey": {
          "anyOf": [
            {
              "$ref": "#/$defs/chromaKey"
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "zoom": {
          "anyOf": [
            {
              "$ref": "#/$defs/zoom"
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "radius": {
          "anyOf": [
            {
              "$ref": "#/$defs/radius"
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "videoBegin": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "description": "Trim: source time (s) where playback starts.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "videoEnd": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "description": "Trim: source time (s) where playback ends.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "videoDuration": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0.1,
              "description": "Play only this many seconds of the source.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "volume": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "maximum": 2,
              "description": "Clip audio volume, 0..2 (1 = original level)."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "speed": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0.1,
              "maximum": 10,
              "description": "Playback speed multiplier, 0.1..10."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "transition": {
          "anyOf": [
            {
              "anyOf": [
                {
                  "enum": [
                    "fade",
                    "fadeblack",
                    "fadewhite",
                    "distance",
                    "wipeleft",
                    "wiperight",
                    "wipeup",
                    "wipedown",
                    "slideleft",
                    "slideright",
                    "slideup",
                    "slidedown",
                    "smoothleft",
                    "smoothright",
                    "smoothup",
                    "smoothdown",
                    "circlecrop",
                    "rectcrop",
                    "circleclose",
                    "circleopen",
                    "horzclose",
                    "horzopen",
                    "vertclose",
                    "vertopen",
                    "diagbl",
                    "diagbr",
                    "diagtl",
                    "diagtr",
                    "hlslice",
                    "hrslice",
                    "vuslice",
                    "vdslice",
                    "dissolve",
                    "pixelize",
                    "radial",
                    "hblur",
                    "wipetl",
                    "wipetr",
                    "wipebl",
                    "wipebr",
                    "fadegrays",
                    "zoomin",
                    "hlwind",
                    "hrwind",
                    "squeezeh",
                    "squeezev",
                    "fadefast",
                    "fadeslow",
                    "vuwind",
                    "vdwind",
                    "coverleft",
                    "coverright",
                    "coverup",
                    "coverdown",
                    "revealleft",
                    "revealright",
                    "revealup",
                    "revealdown"
                  ]
                },
                {
                  "type": "null"
                }
              ],
              "description": "Transition into the linked clip (see transitionId)."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "transitionDuration": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "description": "Transition length in seconds.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "transitionId": {
          "anyOf": [
            {
              "type": "string",
              "pattern": "^[a-zA-Z0-9_-]+$",
              "maxLength": 100,
              "description": "id of the clip this transition links to."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "frameRate": {
          "anyOf": [
            {
              "type": "integer",
              "minimum": 1,
              "maximum": 60,
              "description": "Override source frame rate (1..60)."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "id": {
          "anyOf": [
            {
              "type": "string",
              "pattern": "^[a-zA-Z0-9_-]+$",
              "description": "Clip id (referenced by transitionId)."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "hasAudio": {
          "anyOf": [
            {
              "type": "boolean",
              "description": "Hint that the source has an audio stream."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "condition": {
          "description": "A literal flag or placeholder. false, 0, an empty string, null or an absent resolved value prune this item. Expressions are not supported."
        }
      },
      "required": [
        "type",
        "src"
      ],
      "additionalProperties": false
    },
    "gifElement": {
      "type": "object",
      "title": "GIF element",
      "description": "An animated GIF placed on the canvas (video projects only).",
      "properties": {
        "type": {
          "type": "string",
          "pattern": "^[Gg][Ii][Ff]$",
          "description": "\"GIF\" (case-insensitive)."
        },
        "x": {
          "anyOf": [
            {
              "type": "number",
              "description": "Anchor-point X in canvas px (may be negative). ONLY used when position is \"custom\" — every other preset OVERWRITES x/y.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "y": {
          "anyOf": [
            {
              "type": "number",
              "description": "Anchor-point Y in canvas px (may be negative). ONLY used when position is \"custom\" — every other preset OVERWRITES x/y.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "width": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 1,
              "description": "Element width in px (plan-limited).",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "height": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 1,
              "description": "Element height in px (plan-limited).",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "position": {
          "anyOf": [
            {
              "enum": [
                "top-left",
                "top-center",
                "top-right",
                "center-left",
                "center-center",
                "center-right",
                "bottom-right",
                "bottom-center",
                "bottom-left",
                "custom"
              ],
              "description": "Placement preset. Sets BOTH the canvas point AND the default anchor (e.g. \"bottom-center\" puts the element's bottom edge flush with the frame bottom — no margin). x/y are IGNORED unless \"custom\". Two elements with the same preset render exactly stacked."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "anchor": {
          "anyOf": [
            {
              "enum": [
                "top-left",
                "top-center",
                "top-right",
                "center-left",
                "center-center",
                "center-right",
                "bottom-right",
                "bottom-center",
                "bottom-left",
                "custom"
              ],
              "description": "Which point of the ELEMENT sits at the position point. Defaults to the position preset itself (\"custom\" defaults to top-left)."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "resize": {
          "anyOf": [
            {
              "enum": [
                "contain",
                "cover"
              ],
              "description": "How media fits its frame: contain (letterbox) or cover (center-crop). With explicit width/height the media fits INTO that box (CSS object-fit); without a box it is sized against the full canvas."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "enterBegin": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "description": "Seconds when the element starts appearing (enter animation start).",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "enterEnd": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "description": "Seconds when the enter animation finishes. Must be >= enterBegin.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "exitBegin": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "description": "Seconds when the exit animation starts.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "exitEnd": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "description": "Seconds when the element is fully gone. Must be >= exitBegin.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "opacity": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "maximum": 1,
              "description": "Element opacity, 0..1."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "angle": {
          "anyOf": [
            {
              "type": "number",
              "minimum": -360,
              "maximum": 360,
              "description": "Rotation in degrees."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "flipV": {
          "anyOf": [
            {
              "type": "boolean",
              "description": "Flip vertically."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "flipH": {
          "anyOf": [
            {
              "type": "boolean",
              "description": "Flip horizontally."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "track": {
          "anyOf": [
            {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000000,
              "description": "Z-order track; higher tracks render on top."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "enterAnimation": {
          "anyOf": [
            {
              "anyOf": [
                {
                  "enum": [
                    "fade",
                    "fadeblack",
                    "fadewhite",
                    "distance",
                    "wipeleft",
                    "wiperight",
                    "wipeup",
                    "wipedown",
                    "slideleft",
                    "slideright",
                    "slideup",
                    "slidedown",
                    "smoothleft",
                    "smoothright",
                    "smoothup",
                    "smoothdown",
                    "circlecrop",
                    "rectcrop",
                    "circleclose",
                    "circleopen",
                    "horzclose",
                    "horzopen",
                    "vertclose",
                    "vertopen",
                    "diagbl",
                    "diagbr",
                    "diagtl",
                    "diagtr",
                    "hlslice",
                    "hrslice",
                    "vuslice",
                    "vdslice",
                    "dissolve",
                    "pixelize",
                    "radial",
                    "hblur",
                    "wipetl",
                    "wipetr",
                    "wipebl",
                    "wipebr",
                    "fadegrays",
                    "zoomin",
                    "hlwind",
                    "hrwind",
                    "squeezeh",
                    "squeezev",
                    "fadefast",
                    "fadeslow",
                    "vuwind",
                    "vdwind",
                    "coverleft",
                    "coverright",
                    "coverup",
                    "coverdown",
                    "revealleft",
                    "revealright",
                    "revealup",
                    "revealdown"
                  ]
                },
                {
                  "type": "null"
                }
              ],
              "description": "Enter animation effect (xfade family) or null."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "exitAnimation": {
          "anyOf": [
            {
              "anyOf": [
                {
                  "enum": [
                    "fade",
                    "fadeblack",
                    "fadewhite",
                    "distance",
                    "wipeleft",
                    "wiperight",
                    "wipeup",
                    "wipedown",
                    "slideleft",
                    "slideright",
                    "slideup",
                    "slidedown",
                    "smoothleft",
                    "smoothright",
                    "smoothup",
                    "smoothdown",
                    "circlecrop",
                    "rectcrop",
                    "circleclose",
                    "circleopen",
                    "horzclose",
                    "horzopen",
                    "vertclose",
                    "vertopen",
                    "diagbl",
                    "diagbr",
                    "diagtl",
                    "diagtr",
                    "hlslice",
                    "hrslice",
                    "vuslice",
                    "vdslice",
                    "dissolve",
                    "pixelize",
                    "radial",
                    "hblur",
                    "wipetl",
                    "wipetr",
                    "wipebl",
                    "wipebr",
                    "fadegrays",
                    "zoomin",
                    "hlwind",
                    "hrwind",
                    "squeezeh",
                    "squeezev",
                    "fadefast",
                    "fadeslow",
                    "vuwind",
                    "vdwind",
                    "coverleft",
                    "coverright",
                    "coverup",
                    "coverdown",
                    "revealleft",
                    "revealright",
                    "revealup",
                    "revealdown"
                  ]
                },
                {
                  "type": "null"
                }
              ],
              "description": "Exit animation effect (xfade family) or null."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "src": {
          "anyOf": [
            {
              "type": "string",
              "minLength": 1,
              "maxLength": 2048,
              "pattern": "^https?://",
              "description": "Source media URL. Must be a PUBLIC http(s) URL: no credentials, no spaces/backslashes, port 80/443 only, no localhost/.local, no private IPs."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "cropParams": {
          "anyOf": [
            {
              "$ref": "#/$defs/cropParams"
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "filter": {
          "anyOf": [
            {
              "$ref": "#/$defs/filter"
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "chromaKey": {
          "anyOf": [
            {
              "$ref": "#/$defs/chromaKey"
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "zoom": {
          "anyOf": [
            {
              "$ref": "#/$defs/zoom"
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "radius": {
          "anyOf": [
            {
              "$ref": "#/$defs/radius"
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "condition": {
          "description": "A literal flag or placeholder. false, 0, an empty string, null or an absent resolved value prune this item. Expressions are not supported."
        }
      },
      "required": [
        "type",
        "src"
      ],
      "additionalProperties": false
    },
    "svgElement": {
      "type": "object",
      "title": "SVG element",
      "description": "Inline SVG markup drawn on the canvas.",
      "properties": {
        "type": {
          "type": "string",
          "pattern": "^[Ss][Vv][Gg]$",
          "description": "\"SVG\" (case-insensitive)."
        },
        "x": {
          "anyOf": [
            {
              "type": "number",
              "description": "Anchor-point X in canvas px (may be negative). ONLY used when position is \"custom\" — every other preset OVERWRITES x/y.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "y": {
          "anyOf": [
            {
              "type": "number",
              "description": "Anchor-point Y in canvas px (may be negative). ONLY used when position is \"custom\" — every other preset OVERWRITES x/y.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "width": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 1,
              "description": "Element width in px (plan-limited).",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "height": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 1,
              "description": "Element height in px (plan-limited).",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "position": {
          "anyOf": [
            {
              "enum": [
                "top-left",
                "top-center",
                "top-right",
                "center-left",
                "center-center",
                "center-right",
                "bottom-right",
                "bottom-center",
                "bottom-left",
                "custom"
              ],
              "description": "Placement preset. Sets BOTH the canvas point AND the default anchor (e.g. \"bottom-center\" puts the element's bottom edge flush with the frame bottom — no margin). x/y are IGNORED unless \"custom\". Two elements with the same preset render exactly stacked."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "anchor": {
          "anyOf": [
            {
              "enum": [
                "top-left",
                "top-center",
                "top-right",
                "center-left",
                "center-center",
                "center-right",
                "bottom-right",
                "bottom-center",
                "bottom-left",
                "custom"
              ],
              "description": "Which point of the ELEMENT sits at the position point. Defaults to the position preset itself (\"custom\" defaults to top-left)."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "resize": {
          "anyOf": [
            {
              "enum": [
                "contain",
                "cover"
              ],
              "description": "How media fits its frame: contain (letterbox) or cover (center-crop). With explicit width/height the media fits INTO that box (CSS object-fit); without a box it is sized against the full canvas."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "enterBegin": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "description": "Seconds when the element starts appearing (enter animation start).",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "enterEnd": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "description": "Seconds when the enter animation finishes. Must be >= enterBegin.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "exitBegin": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "description": "Seconds when the exit animation starts.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "exitEnd": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "description": "Seconds when the element is fully gone. Must be >= exitBegin.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "opacity": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "maximum": 1,
              "description": "Element opacity, 0..1."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "angle": {
          "anyOf": [
            {
              "type": "number",
              "minimum": -360,
              "maximum": 360,
              "description": "Rotation in degrees."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "flipV": {
          "anyOf": [
            {
              "type": "boolean",
              "description": "Flip vertically."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "flipH": {
          "anyOf": [
            {
              "type": "boolean",
              "description": "Flip horizontally."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "track": {
          "anyOf": [
            {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000000,
              "description": "Z-order track; higher tracks render on top."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "enterAnimation": {
          "anyOf": [
            {
              "anyOf": [
                {
                  "enum": [
                    "fade",
                    "fadeblack",
                    "fadewhite",
                    "distance",
                    "wipeleft",
                    "wiperight",
                    "wipeup",
                    "wipedown",
                    "slideleft",
                    "slideright",
                    "slideup",
                    "slidedown",
                    "smoothleft",
                    "smoothright",
                    "smoothup",
                    "smoothdown",
                    "circlecrop",
                    "rectcrop",
                    "circleclose",
                    "circleopen",
                    "horzclose",
                    "horzopen",
                    "vertclose",
                    "vertopen",
                    "diagbl",
                    "diagbr",
                    "diagtl",
                    "diagtr",
                    "hlslice",
                    "hrslice",
                    "vuslice",
                    "vdslice",
                    "dissolve",
                    "pixelize",
                    "radial",
                    "hblur",
                    "wipetl",
                    "wipetr",
                    "wipebl",
                    "wipebr",
                    "fadegrays",
                    "zoomin",
                    "hlwind",
                    "hrwind",
                    "squeezeh",
                    "squeezev",
                    "fadefast",
                    "fadeslow",
                    "vuwind",
                    "vdwind",
                    "coverleft",
                    "coverright",
                    "coverup",
                    "coverdown",
                    "revealleft",
                    "revealright",
                    "revealup",
                    "revealdown"
                  ]
                },
                {
                  "type": "null"
                }
              ],
              "description": "Enter animation effect (xfade family) or null."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "exitAnimation": {
          "anyOf": [
            {
              "anyOf": [
                {
                  "enum": [
                    "fade",
                    "fadeblack",
                    "fadewhite",
                    "distance",
                    "wipeleft",
                    "wiperight",
                    "wipeup",
                    "wipedown",
                    "slideleft",
                    "slideright",
                    "slideup",
                    "slidedown",
                    "smoothleft",
                    "smoothright",
                    "smoothup",
                    "smoothdown",
                    "circlecrop",
                    "rectcrop",
                    "circleclose",
                    "circleopen",
                    "horzclose",
                    "horzopen",
                    "vertclose",
                    "vertopen",
                    "diagbl",
                    "diagbr",
                    "diagtl",
                    "diagtr",
                    "hlslice",
                    "hrslice",
                    "vuslice",
                    "vdslice",
                    "dissolve",
                    "pixelize",
                    "radial",
                    "hblur",
                    "wipetl",
                    "wipetr",
                    "wipebl",
                    "wipebr",
                    "fadegrays",
                    "zoomin",
                    "hlwind",
                    "hrwind",
                    "squeezeh",
                    "squeezev",
                    "fadefast",
                    "fadeslow",
                    "vuwind",
                    "vdwind",
                    "coverleft",
                    "coverright",
                    "coverup",
                    "coverdown",
                    "revealleft",
                    "revealright",
                    "revealup",
                    "revealdown"
                  ]
                },
                {
                  "type": "null"
                }
              ],
              "description": "Exit animation effect (xfade family) or null."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "svg": {
          "anyOf": [
            {
              "type": "string",
              "minLength": 1,
              "maxLength": 200000,
              "description": "Inline SVG markup starting with \"<svg\". Sanitized: no <script>/<foreignObject>/<iframe>/<object>/<embed>/<audio>/<video>, no on* event attributes, no external url()/href/src (only \"#id\" fragment refs), dimensions <= 4096px, no integers with 8+ digits."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "filter": {
          "anyOf": [
            {
              "$ref": "#/$defs/filter"
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "chromaKey": {
          "anyOf": [
            {
              "$ref": "#/$defs/chromaKey"
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "customCode": {
          "anyOf": [
            {
              "$ref": "#/$defs/customCode"
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "designer": {
          "anyOf": [
            {
              "$ref": "#/$defs/designer"
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "condition": {
          "description": "A literal flag or placeholder. false, 0, an empty string, null or an absent resolved value prune this item. Expressions are not supported."
        }
      },
      "required": [
        "type",
        "svg"
      ],
      "additionalProperties": false
    },
    "textElement": {
      "type": "object",
      "title": "TEXT element",
      "description": "Styled text. Provide plain `text` (no < or > characters) and/or limited `html`. At least one of text/html must be non-empty.",
      "properties": {
        "type": {
          "type": "string",
          "pattern": "^[Tt][Ee][Xx][Tt]$",
          "description": "\"TEXT\" (case-insensitive)."
        },
        "x": {
          "anyOf": [
            {
              "type": "number",
              "description": "Anchor-point X in canvas px (may be negative). ONLY used when position is \"custom\" — every other preset OVERWRITES x/y.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "y": {
          "anyOf": [
            {
              "type": "number",
              "description": "Anchor-point Y in canvas px (may be negative). ONLY used when position is \"custom\" — every other preset OVERWRITES x/y.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "width": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 1,
              "description": "Element width in px (plan-limited).",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "height": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 1,
              "description": "Element height in px (plan-limited).",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "position": {
          "anyOf": [
            {
              "enum": [
                "top-left",
                "top-center",
                "top-right",
                "center-left",
                "center-center",
                "center-right",
                "bottom-right",
                "bottom-center",
                "bottom-left",
                "custom"
              ],
              "description": "Placement preset. Sets BOTH the canvas point AND the default anchor (e.g. \"bottom-center\" puts the element's bottom edge flush with the frame bottom — no margin). x/y are IGNORED unless \"custom\". Two elements with the same preset render exactly stacked."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "anchor": {
          "anyOf": [
            {
              "enum": [
                "top-left",
                "top-center",
                "top-right",
                "center-left",
                "center-center",
                "center-right",
                "bottom-right",
                "bottom-center",
                "bottom-left",
                "custom"
              ],
              "description": "Which point of the ELEMENT sits at the position point. Defaults to the position preset itself (\"custom\" defaults to top-left)."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "resize": {
          "anyOf": [
            {
              "enum": [
                "contain",
                "cover"
              ],
              "description": "How media fits its frame: contain (letterbox) or cover (center-crop). With explicit width/height the media fits INTO that box (CSS object-fit); without a box it is sized against the full canvas."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "enterBegin": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "description": "Seconds when the element starts appearing (enter animation start).",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "enterEnd": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "description": "Seconds when the enter animation finishes. Must be >= enterBegin.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "exitBegin": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "description": "Seconds when the exit animation starts.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "exitEnd": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "description": "Seconds when the element is fully gone. Must be >= exitBegin.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "opacity": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "maximum": 1,
              "description": "Element opacity, 0..1."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "angle": {
          "anyOf": [
            {
              "type": "number",
              "minimum": -360,
              "maximum": 360,
              "description": "Rotation in degrees."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "flipV": {
          "anyOf": [
            {
              "type": "boolean",
              "description": "Flip vertically."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "flipH": {
          "anyOf": [
            {
              "type": "boolean",
              "description": "Flip horizontally."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "track": {
          "anyOf": [
            {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000000,
              "description": "Z-order track; higher tracks render on top."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "enterAnimation": {
          "anyOf": [
            {
              "anyOf": [
                {
                  "enum": [
                    "fade",
                    "fadeblack",
                    "fadewhite",
                    "distance",
                    "wipeleft",
                    "wiperight",
                    "wipeup",
                    "wipedown",
                    "slideleft",
                    "slideright",
                    "slideup",
                    "slidedown",
                    "smoothleft",
                    "smoothright",
                    "smoothup",
                    "smoothdown",
                    "circlecrop",
                    "rectcrop",
                    "circleclose",
                    "circleopen",
                    "horzclose",
                    "horzopen",
                    "vertclose",
                    "vertopen",
                    "diagbl",
                    "diagbr",
                    "diagtl",
                    "diagtr",
                    "hlslice",
                    "hrslice",
                    "vuslice",
                    "vdslice",
                    "dissolve",
                    "pixelize",
                    "radial",
                    "hblur",
                    "wipetl",
                    "wipetr",
                    "wipebl",
                    "wipebr",
                    "fadegrays",
                    "zoomin",
                    "hlwind",
                    "hrwind",
                    "squeezeh",
                    "squeezev",
                    "fadefast",
                    "fadeslow",
                    "vuwind",
                    "vdwind",
                    "coverleft",
                    "coverright",
                    "coverup",
                    "coverdown",
                    "revealleft",
                    "revealright",
                    "revealup",
                    "revealdown"
                  ]
                },
                {
                  "type": "null"
                }
              ],
              "description": "Enter animation effect (xfade family) or null."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "exitAnimation": {
          "anyOf": [
            {
              "anyOf": [
                {
                  "enum": [
                    "fade",
                    "fadeblack",
                    "fadewhite",
                    "distance",
                    "wipeleft",
                    "wiperight",
                    "wipeup",
                    "wipedown",
                    "slideleft",
                    "slideright",
                    "slideup",
                    "slidedown",
                    "smoothleft",
                    "smoothright",
                    "smoothup",
                    "smoothdown",
                    "circlecrop",
                    "rectcrop",
                    "circleclose",
                    "circleopen",
                    "horzclose",
                    "horzopen",
                    "vertclose",
                    "vertopen",
                    "diagbl",
                    "diagbr",
                    "diagtl",
                    "diagtr",
                    "hlslice",
                    "hrslice",
                    "vuslice",
                    "vdslice",
                    "dissolve",
                    "pixelize",
                    "radial",
                    "hblur",
                    "wipetl",
                    "wipetr",
                    "wipebl",
                    "wipebr",
                    "fadegrays",
                    "zoomin",
                    "hlwind",
                    "hrwind",
                    "squeezeh",
                    "squeezev",
                    "fadefast",
                    "fadeslow",
                    "vuwind",
                    "vdwind",
                    "coverleft",
                    "coverright",
                    "coverup",
                    "coverdown",
                    "revealleft",
                    "revealright",
                    "revealup",
                    "revealdown"
                  ]
                },
                {
                  "type": "null"
                }
              ],
              "description": "Exit animation effect (xfade family) or null."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "text": {
          "anyOf": [
            {
              "type": "string",
              "maxLength": 20000,
              "description": "Plain text content. HTML markup (< or >) is rejected — use `html` for markup."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "html": {
          "anyOf": [
            {
              "type": "string",
              "maxLength": 200000,
              "description": "Limited HTML. Allowed tags: b, strong, i, em, u, s, br, span, div, p, ul, ol, li, img, canvas, svg (+ svg geometry). Allowed attributes: style, class, src, alt, width, height. No <script>/<style>, no event handlers, no external CSS url()."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "style": {
          "anyOf": [
            {
              "type": "object",
              "maxProperties": 120,
              "description": "CSS style object applied to the text container (camelCase or kebab-case keys, and --custom-properties). LAYOUT MODEL: the container uses box-sizing: content-box, so padding/border render OUTSIDE the declared width/height and overflow gets cut — for cards/pills set the full size via width/height and center content with display:flex + alignItems:center + justifyContent:center instead of padding. Text sits at the TOP of the box unless flex-centered. Values must not contain url(), @import, expression(), comments or other dangerous tokens. Max value length 4000 chars."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "fitToBox": {
          "anyOf": [
            {
              "type": "boolean",
              "description": "Scale the typography down just enough that the painted text stays inside the declared width/height instead of being clipped at the element's edge. No effect when the content already fits, so an unchanged template renders identically."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "customCode": {
          "anyOf": [
            {
              "$ref": "#/$defs/customCode"
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "designer": {
          "anyOf": [
            {
              "$ref": "#/$defs/designer"
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "condition": {
          "description": "A literal flag or placeholder. false, 0, an empty string, null or an absent resolved value prune this item. Expressions are not supported."
        }
      },
      "required": [
        "type"
      ],
      "additionalProperties": false,
      "anyOf": [
        {
          "required": [
            "text"
          ],
          "properties": {
            "text": {
              "anyOf": [
                {
                  "type": "string",
                  "pattern": "\\S"
                },
                {
                  "type": "string",
                  "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                }
              ]
            }
          }
        },
        {
          "required": [
            "html"
          ],
          "properties": {
            "html": {
              "anyOf": [
                {
                  "type": "string",
                  "pattern": "\\S"
                },
                {
                  "type": "string",
                  "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                }
              ]
            }
          }
        },
        {
          "required": [
            "condition"
          ]
        }
      ]
    },
    "audioItem": {
      "type": "object",
      "title": "Audio item",
      "description": "A soundtrack / voice-over entry for `audios` (video projects only).",
      "properties": {
        "src": {
          "anyOf": [
            {
              "type": "string",
              "minLength": 1,
              "maxLength": 2048,
              "pattern": "^https?://",
              "description": "Audio file URL (mp3/wav/...). Must be a PUBLIC http(s) URL: no credentials, no spaces/backslashes, port 80/443 only, no localhost/.local, no private IPs."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "matchDuration": {
          "anyOf": [
            {
              "type": "boolean",
              "description": "Follow the containing scene/project length without extending automatic duration. Overrides exit."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "enter": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "description": "Timeline second the audio starts playing.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "exit": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "description": "Timeline second the audio stops. Must be >= enter.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "volume": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "maximum": 2
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "speed": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0.1,
              "maximum": 10
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "audioBegin": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "description": "Trim: source second where playback starts.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "audioEnd": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "description": "Trim: source second where playback ends. Must be >= audioBegin.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "audioDuration": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "description": "Play only this many seconds of the source.",
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "track": {
          "anyOf": [
            {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000000,
              "description": "Mix track index; audio tracks mix together."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "condition": {
          "description": "A literal flag or placeholder. false, 0, an empty string, null or an absent resolved value prune this item. Expressions are not supported."
        }
      },
      "additionalProperties": false
    },
    "scene": {
      "type": "object",
      "title": "Scene",
      "description": "A timeline segment. Scenes play sequentially; a scene's `transition` blends into the NEXT scene (xfade overlap is subtracted from the total duration).",
      "properties": {
        "id": {
          "anyOf": [
            {
              "type": "string",
              "pattern": "^[a-zA-Z0-9_-]+$",
              "maxLength": 100,
              "description": "Optional scene ID. Explicit IDs must be unique within the project."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "duration": {
          "anyOf": [
            {
              "description": "Seconds; -1 (or omitted) auto-computes from the scene's content.",
              "anyOf": [
                {
                  "const": -1
                },
                {
                  "type": "number",
                  "minimum": 0.1,
                  "x-zvid-plan-dependent": true
                }
              ]
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "transition": {
          "anyOf": [
            {
              "anyOf": [
                {
                  "enum": [
                    "fade",
                    "fadeblack",
                    "fadewhite",
                    "distance",
                    "wipeleft",
                    "wiperight",
                    "wipeup",
                    "wipedown",
                    "slideleft",
                    "slideright",
                    "slideup",
                    "slidedown",
                    "smoothleft",
                    "smoothright",
                    "smoothup",
                    "smoothdown",
                    "circlecrop",
                    "rectcrop",
                    "circleclose",
                    "circleopen",
                    "horzclose",
                    "horzopen",
                    "vertclose",
                    "vertopen",
                    "diagbl",
                    "diagbr",
                    "diagtl",
                    "diagtr",
                    "hlslice",
                    "hrslice",
                    "vuslice",
                    "vdslice",
                    "dissolve",
                    "pixelize",
                    "radial",
                    "hblur",
                    "wipetl",
                    "wipetr",
                    "wipebl",
                    "wipebr",
                    "fadegrays",
                    "zoomin",
                    "hlwind",
                    "hrwind",
                    "squeezeh",
                    "squeezev",
                    "fadefast",
                    "fadeslow",
                    "vuwind",
                    "vdwind",
                    "coverleft",
                    "coverright",
                    "coverup",
                    "coverdown",
                    "revealleft",
                    "revealright",
                    "revealup",
                    "revealdown"
                  ]
                },
                {
                  "type": "null"
                }
              ],
              "description": "Transition into the next scene."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "transitionId": {
          "anyOf": [
            {
              "anyOf": [
                {
                  "type": "string",
                  "pattern": "^[a-zA-Z0-9_-]+$",
                  "maxLength": 100
                },
                {
                  "type": "null"
                }
              ]
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "transitionDuration": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "maximum": 60,
              "description": "Transition overlap in seconds (default 0.5)."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "backgroundColor": {
          "anyOf": [
            {
              "type": "string",
              "pattern": "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$",
              "description": "Scene background (#rgb or #rrggbb)."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "visuals": {
          "anyOf": [
            {
              "type": "array",
              "items": {
                "$ref": "#/$defs/visual"
              }
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "audios": {
          "anyOf": [
            {
              "type": "array",
              "items": {
                "$ref": "#/$defs/audioItem"
              }
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "variables": {
          "type": "object",
          "additionalProperties": true,
          "description": "Default variable values; request variables override these. Scene defaults shadow project values inside that scene."
        },
        "iterate": {
          "type": "string",
          "description": "Name or dotted path of an array variable; one scene is generated per item."
        },
        "iterateAs": {
          "type": "string",
          "pattern": "^[A-Za-z][A-Za-z0-9_]*$",
          "not": {
            "enum": [
              "prototype",
              "constructor"
            ]
          },
          "default": "item"
        },
        "condition": {
          "description": "A literal flag or placeholder. false, 0, an empty string, null or an absent resolved value prune this item. Expressions are not supported."
        }
      },
      "additionalProperties": false
    },
    "subtitle": {
      "type": "object",
      "title": "Subtitle",
      "description": "Burned-in subtitles. Content: EXACTLY ONE of `src` (SRT/VTT URL) or `captions`. Style: v2 flat keys (animation/font/stroke/background/...) OR the legacy `styles` block — not both.",
      "properties": {
        "src": {
          "anyOf": [
            {
              "type": "string",
              "minLength": 1,
              "maxLength": 2048,
              "pattern": "^https?://",
              "description": "SRT or VTT file URL. Must be a PUBLIC http(s) URL: no credentials, no spaces/backslashes, port 80/443 only, no localhost/.local, no private IPs."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "captions": {
          "anyOf": [
            {
              "type": "array",
              "minItems": 1,
              "items": {
                "type": "object",
                "properties": {
                  "start": {
                    "anyOf": [
                      {
                        "type": "number",
                        "minimum": 0,
                        "description": "Caption start time in seconds.",
                        "x-zvid-plan-dependent": true
                      },
                      {
                        "type": "string",
                        "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                      }
                    ]
                  },
                  "end": {
                    "anyOf": [
                      {
                        "type": "number",
                        "minimum": 0,
                        "description": "Caption end time in seconds.",
                        "x-zvid-plan-dependent": true
                      },
                      {
                        "type": "string",
                        "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                      }
                    ]
                  },
                  "text": {
                    "anyOf": [
                      {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 1000
                      },
                      {
                        "type": "string",
                        "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                      }
                    ]
                  },
                  "words": {
                    "anyOf": [
                      {
                        "type": "array",
                        "minItems": 1,
                        "items": {
                          "type": "object",
                          "properties": {
                            "start": {
                              "anyOf": [
                                {
                                  "type": "number",
                                  "minimum": 0,
                                  "x-zvid-plan-dependent": true
                                },
                                {
                                  "type": "string",
                                  "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                                }
                              ]
                            },
                            "end": {
                              "anyOf": [
                                {
                                  "type": "number",
                                  "minimum": 0,
                                  "x-zvid-plan-dependent": true
                                },
                                {
                                  "type": "string",
                                  "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                                }
                              ]
                            },
                            "text": {
                              "anyOf": [
                                {
                                  "type": "string",
                                  "minLength": 1,
                                  "maxLength": 100
                                },
                                {
                                  "type": "string",
                                  "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                                }
                              ]
                            }
                          },
                          "required": [
                            "start",
                            "end",
                            "text"
                          ],
                          "additionalProperties": false
                        },
                        "description": "Per-word timings; auto-distributed from `text` when omitted."
                      },
                      {
                        "type": "string",
                        "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                      }
                    ]
                  }
                },
                "required": [
                  "start",
                  "end"
                ],
                "anyOf": [
                  {
                    "required": [
                      "text"
                    ]
                  },
                  {
                    "required": [
                      "words"
                    ]
                  }
                ],
                "additionalProperties": false
              },
              "description": "Inline captions (plan-limited count)."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "maxWordsPerLine": {
          "anyOf": [
            {
              "type": "integer",
              "minimum": 1,
              "maximum": 20,
              "description": "Re-chunk captions to at most N words per line."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "animation": {
          "anyOf": [
            {
              "enum": [
                "normal",
                "none",
                "one-word",
                "karaoke",
                "progressive",
                "highlight",
                "fill",
                "pop",
                "bounce",
                "fade",
                "slide",
                "typewriter"
              ],
              "description": "Caption animation mode."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "direction": {
          "anyOf": [
            {
              "enum": [
                "up",
                "down",
                "left",
                "right"
              ],
              "description": "Slide direction when animation is \"slide\"."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "font": {
          "anyOf": [
            {
              "type": "object",
              "minProperties": 1,
              "properties": {
                "family": {
                  "anyOf": [
                    {
                      "type": "string",
                      "pattern": "^[a-zA-Z0-9_\\- ]+$",
                      "maxLength": 1000
                    },
                    {
                      "type": "string",
                      "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                    }
                  ]
                },
                "size": {
                  "anyOf": [
                    {
                      "type": "number",
                      "minimum": 1,
                      "maximum": 1000
                    },
                    {
                      "type": "string",
                      "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                    }
                  ]
                },
                "color": {
                  "anyOf": [
                    {
                      "type": "string",
                      "pattern": "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$",
                      "description": "#rrggbb or #rrggbbaa"
                    },
                    {
                      "type": "string",
                      "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                    }
                  ]
                },
                "bold": {
                  "anyOf": [
                    {
                      "type": "boolean"
                    },
                    {
                      "type": "string",
                      "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                    }
                  ]
                },
                "italic": {
                  "anyOf": [
                    {
                      "type": "boolean"
                    },
                    {
                      "type": "string",
                      "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                    }
                  ]
                },
                "transform": {
                  "anyOf": [
                    {
                      "enum": [
                        "uppercase",
                        "lowercase",
                        "capitalize"
                      ]
                    },
                    {
                      "type": "string",
                      "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                    }
                  ]
                }
              },
              "additionalProperties": false
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "stroke": {
          "anyOf": [
            {
              "type": "object",
              "properties": {
                "color": {
                  "anyOf": [
                    {
                      "type": "string",
                      "pattern": "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$",
                      "description": "#rrggbb or #rrggbbaa"
                    },
                    {
                      "type": "string",
                      "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                    }
                  ]
                },
                "width": {
                  "anyOf": [
                    {
                      "type": "number",
                      "minimum": 0,
                      "maximum": 100
                    },
                    {
                      "type": "string",
                      "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                    }
                  ]
                }
              },
              "required": [
                "color",
                "width"
              ],
              "additionalProperties": false
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "background": {
          "anyOf": [
            {
              "type": "object",
              "minProperties": 1,
              "properties": {
                "color": {
                  "anyOf": [
                    {
                      "type": "string",
                      "pattern": "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$",
                      "description": "#rrggbb or #rrggbbaa"
                    },
                    {
                      "type": "string",
                      "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                    }
                  ]
                },
                "opacity": {
                  "anyOf": [
                    {
                      "type": "number",
                      "minimum": 0,
                      "maximum": 1
                    },
                    {
                      "type": "string",
                      "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                    }
                  ]
                },
                "padding": {
                  "anyOf": [
                    {
                      "type": "number",
                      "minimum": 0,
                      "maximum": 200
                    },
                    {
                      "type": "string",
                      "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                    }
                  ]
                },
                "radius": {
                  "anyOf": [
                    {
                      "type": "number",
                      "minimum": 0,
                      "maximum": 200
                    },
                    {
                      "type": "string",
                      "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                    }
                  ]
                }
              },
              "additionalProperties": false
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "activeWord": {
          "anyOf": [
            {
              "type": "object",
              "minProperties": 1,
              "description": "Highlight styling for the currently spoken word.",
              "properties": {
                "color": {
                  "anyOf": [
                    {
                      "type": "string",
                      "pattern": "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$",
                      "description": "#rrggbb or #rrggbbaa"
                    },
                    {
                      "type": "string",
                      "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                    }
                  ]
                },
                "background": {
                  "anyOf": [
                    {
                      "type": "string",
                      "pattern": "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$",
                      "description": "#rrggbb or #rrggbbaa"
                    },
                    {
                      "type": "string",
                      "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                    }
                  ]
                },
                "radius": {
                  "anyOf": [
                    {
                      "type": "number",
                      "minimum": 0,
                      "maximum": 200
                    },
                    {
                      "type": "string",
                      "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                    }
                  ]
                }
              },
              "additionalProperties": false
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "position": {
          "anyOf": [
            {
              "enum": [
                "top-left",
                "top-center",
                "top-right",
                "bottom-left",
                "bottom-center",
                "bottom-right",
                "center-center",
                "center-left",
                "center-right",
                "top",
                "center",
                "bottom"
              ]
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "margin": {
          "anyOf": [
            {
              "type": "object",
              "minProperties": 1,
              "properties": {
                "x": {
                  "anyOf": [
                    {
                      "type": "integer",
                      "minimum": 0,
                      "x-zvid-plan-dependent": true
                    },
                    {
                      "type": "string",
                      "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                    }
                  ]
                },
                "y": {
                  "anyOf": [
                    {
                      "type": "integer",
                      "minimum": 0,
                      "x-zvid-plan-dependent": true
                    },
                    {
                      "type": "string",
                      "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                    }
                  ]
                }
              },
              "additionalProperties": false
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "styles": {
          "anyOf": [
            {
              "type": "object",
              "description": "LEGACY style block — prefer the flat v2 keys. Cannot be combined with them.",
              "properties": {
                "color": {
                  "anyOf": [
                    {
                      "type": "string",
                      "pattern": "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$",
                      "description": "#rrggbb or #rrggbbaa"
                    },
                    {
                      "type": "string",
                      "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                    }
                  ]
                },
                "background": {
                  "anyOf": [
                    {
                      "type": "string",
                      "pattern": "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$",
                      "description": "#rrggbb or #rrggbbaa"
                    },
                    {
                      "type": "string",
                      "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                    }
                  ]
                },
                "backgroundPadding": {
                  "anyOf": [
                    {
                      "type": "number",
                      "minimum": 0,
                      "maximum": 200
                    },
                    {
                      "type": "string",
                      "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                    }
                  ]
                },
                "backgroundRadius": {
                  "anyOf": [
                    {
                      "type": "number",
                      "minimum": 0,
                      "maximum": 200
                    },
                    {
                      "type": "string",
                      "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                    }
                  ]
                },
                "isBold": {
                  "anyOf": [
                    {
                      "type": "boolean"
                    },
                    {
                      "type": "string",
                      "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                    }
                  ]
                },
                "isItalic": {
                  "anyOf": [
                    {
                      "type": "boolean"
                    },
                    {
                      "type": "string",
                      "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                    }
                  ]
                },
                "fontSize": {
                  "anyOf": [
                    {
                      "type": "number",
                      "minimum": 1,
                      "maximum": 1000
                    },
                    {
                      "type": "string",
                      "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                    }
                  ]
                },
                "fontFamily": {
                  "anyOf": [
                    {
                      "type": "string",
                      "pattern": "^[a-zA-Z0-9_\\- ]+$",
                      "maxLength": 1000
                    },
                    {
                      "type": "string",
                      "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                    }
                  ]
                },
                "textTransform": {
                  "anyOf": [
                    {
                      "enum": [
                        "uppercase",
                        "lowercase",
                        "capitalize"
                      ]
                    },
                    {
                      "type": "string",
                      "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                    }
                  ]
                },
                "outline": {
                  "anyOf": [
                    {
                      "type": "object",
                      "properties": {
                        "width": {
                          "anyOf": [
                            {
                              "type": "number",
                              "minimum": 0,
                              "maximum": 100
                            },
                            {
                              "type": "string",
                              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                            }
                          ]
                        },
                        "color": {
                          "anyOf": [
                            {
                              "type": "string",
                              "pattern": "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$",
                              "description": "#rrggbb or #rrggbbaa"
                            },
                            {
                              "type": "string",
                              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                            }
                          ]
                        }
                      },
                      "required": [
                        "width",
                        "color"
                      ],
                      "additionalProperties": false
                    },
                    {
                      "type": "string",
                      "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                    }
                  ]
                },
                "position": {
                  "anyOf": [
                    {
                      "enum": [
                        "top-left",
                        "top-center",
                        "top-right",
                        "bottom-left",
                        "bottom-center",
                        "bottom-right",
                        "center-center",
                        "center-left",
                        "center-right"
                      ]
                    },
                    {
                      "type": "string",
                      "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                    }
                  ]
                },
                "marginV": {
                  "anyOf": [
                    {
                      "type": "integer",
                      "minimum": 0,
                      "x-zvid-plan-dependent": true
                    },
                    {
                      "type": "string",
                      "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                    }
                  ]
                },
                "marginH": {
                  "anyOf": [
                    {
                      "type": "integer",
                      "minimum": 0,
                      "x-zvid-plan-dependent": true
                    },
                    {
                      "type": "string",
                      "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                    }
                  ]
                },
                "mode": {
                  "anyOf": [
                    {
                      "enum": [
                        "normal",
                        "none",
                        "one-word",
                        "karaoke",
                        "progressive",
                        "highlight",
                        "fill",
                        "pop",
                        "bounce",
                        "fade",
                        "slide",
                        "typewriter"
                      ]
                    },
                    {
                      "type": "string",
                      "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                    }
                  ]
                },
                "slideDirection": {
                  "anyOf": [
                    {
                      "enum": [
                        "up",
                        "down",
                        "left",
                        "right"
                      ]
                    },
                    {
                      "type": "string",
                      "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                    }
                  ]
                },
                "activeWord": {
                  "anyOf": [
                    {
                      "type": "object",
                      "minProperties": 1,
                      "properties": {
                        "color": {
                          "anyOf": [
                            {
                              "type": "string",
                              "pattern": "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$",
                              "description": "#rrggbb or #rrggbbaa"
                            },
                            {
                              "type": "string",
                              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                            }
                          ]
                        },
                        "background": {
                          "anyOf": [
                            {
                              "type": "string",
                              "pattern": "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$",
                              "description": "#rrggbb or #rrggbbaa"
                            },
                            {
                              "type": "string",
                              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                            }
                          ]
                        },
                        "radius": {
                          "anyOf": [
                            {
                              "type": "number",
                              "minimum": 0,
                              "maximum": 200
                            },
                            {
                              "type": "string",
                              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                            }
                          ]
                        }
                      },
                      "additionalProperties": false
                    },
                    {
                      "type": "string",
                      "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                    }
                  ]
                }
              },
              "additionalProperties": false
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        }
      },
      "additionalProperties": false,
      "oneOf": [
        {
          "required": [
            "src"
          ],
          "not": {
            "required": [
              "captions"
            ]
          }
        },
        {
          "required": [
            "captions"
          ],
          "not": {
            "required": [
              "src"
            ]
          }
        }
      ],
      "dependentSchemas": {
        "styles": {
          "properties": {
            "animation": false,
            "direction": false,
            "font": false,
            "stroke": false,
            "background": false,
            "activeWord": false,
            "position": false,
            "margin": false
          }
        }
      }
    },
    "cropParams": {
      "type": "object",
      "description": "Source crop rectangle in source-media pixels.",
      "properties": {
        "x": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "y": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "width": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 1,
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "height": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 1,
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        }
      },
      "required": [
        "x",
        "y",
        "width",
        "height"
      ],
      "additionalProperties": false
    },
    "radius": {
      "type": "object",
      "description": "Rounded-corner radii in px (tl/tr/bl/br).",
      "properties": {
        "tl": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "tr": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "bl": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "br": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "x-zvid-plan-dependent": true
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        }
      },
      "additionalProperties": false
    },
    "filter": {
      "type": "object",
      "description": "CSS-like color filters.",
      "properties": {
        "brightness": {
          "anyOf": [
            {
              "type": "number",
              "minimum": -100,
              "maximum": 100
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "contrast": {
          "anyOf": [
            {
              "type": "number",
              "minimum": -100,
              "maximum": 100
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "saturate": {
          "anyOf": [
            {
              "type": "number",
              "minimum": -100,
              "maximum": 100
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "hue-rotate": {
          "anyOf": [
            {
              "type": "string",
              "pattern": "^-?(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:deg)?$",
              "description": "Degrees, preferably with a \"deg\" suffix (e.g. \"90deg\"). Legacy numeric strings such as \"90\" remain accepted."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "blur": {
          "anyOf": [
            {
              "description": "Unitless 0..100 amount. Numeric strings remain accepted for backward compatibility; CSS lengths such as 4px are not supported.",
              "anyOf": [
                {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100
                },
                {
                  "type": "string",
                  "pattern": "^(?:0*(?:100(?:\\.0+)?|\\d{1,2}(?:\\.\\d+)?|\\.\\d+))$"
                }
              ]
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "invert": {
          "anyOf": [
            {
              "description": "Boolean or partial inversion amount from 0 to 1.",
              "anyOf": [
                {
                  "type": "boolean"
                },
                {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 1
                }
              ]
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "colorTint": {
          "anyOf": [
            {
              "type": "string",
              "pattern": "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$",
              "description": "Tint color (#rgb or #rrggbb)."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        }
      },
      "additionalProperties": false
    },
    "chromaKey": {
      "type": "object",
      "description": "Green-screen keying.",
      "properties": {
        "color": {
          "anyOf": [
            {
              "type": "string",
              "pattern": "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$",
              "description": "Key color to remove (required)."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "similarity": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "maximum": 100
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "blend": {
          "anyOf": [
            {
              "type": "number",
              "minimum": 0,
              "maximum": 100
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        }
      },
      "required": [
        "color"
      ],
      "additionalProperties": false
    },
    "zoom": {
      "description": "Ken Burns zoom: true (default 1.2x depth) or { \"depth\": 1..10 }.",
      "anyOf": [
        {
          "type": "boolean"
        },
        {
          "type": "object",
          "properties": {
            "depth": {
              "anyOf": [
                {
                  "type": "number",
                  "minimum": 1,
                  "maximum": 10
                },
                {
                  "type": "string",
                  "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
                }
              ]
            }
          },
          "additionalProperties": false
        }
      ]
    },
    "customCode": {
      "type": "object",
      "description": "Sandboxed styling/animation code run inside the render browser (Design Studio). No network/filesystem/storage/navigation APIs — see validation notes.",
      "properties": {
        "css": {
          "anyOf": [
            {
              "type": "string",
              "maxLength": 200000
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "js": {
          "anyOf": [
            {
              "type": "string",
              "maxLength": 200000
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        },
        "animationDuration": {
          "anyOf": [
            {
              "type": "number",
              "exclusiveMinimum": 0,
              "maximum": 15,
              "description": "Seconds the customCode animation runs (max 15)."
            },
            {
              "type": "string",
              "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
            }
          ]
        }
      },
      "additionalProperties": false
    },
    "designer": {
      "type": "object",
      "description": "Opaque Design Studio round-trip metadata (max 200k chars serialized). Never rendered; keeps 'edit design' working."
    }
  },
  "x-zvid-source": {
    "schemaVersion": "1.0.0",
    "source": "zvid-integrations/schema/src/zvidSchema.ts via orch/services/zvidSchema.generated.js",
    "sha256": "63b17aa2e6edc73fc25d571df058d372f1e6d51fe39f2b2b1071d9e06add7a3d"
  }
}
