{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://docs.zvid.io/schemas/render-request.schema.json",
  "title": "Zvid render request envelope",
  "type": "object",
  "additionalProperties": false,
  "description": "Provide either `payload` (a full project) or `template` (a stored template id) — not both.",
  "properties": {
    "payload": {
      "$ref": "#/$defs/RenderPayload"
    },
    "template": {
      "type": "string",
      "description": "Stored template id (`tpl_…`). Mutually exclusive with `payload`.",
      "example": "tpl_rAequJHzUETcLAMfrezk",
      "pattern": "^tpl_[A-Za-z0-9]{20}$"
    },
    "variables": {
      "type": "object",
      "additionalProperties": true,
      "description": "Request-time values override project defaults for BOTH inline payloads and stored templates."
    },
    "overrides": {
      "$ref": "#/$defs/RenderOverrides"
    },
    "webhookUrl": {
      "type": "string",
      "format": "uri",
      "maxLength": 2048,
      "description": "One-off unsigned POST for this job on completion or failure. Use a registered webhook for HMAC signatures. The hosted API requires a public HTTPS address; private destinations are rejected."
    },
    "jobId": {
      "type": "string",
      "format": "uuid",
      "description": "Optional caller-supplied UUID. This is not a documented idempotency guarantee: do not blindly resubmit after a timeout; first check this job ID.",
      "example": "550e8400-e29b-41d4-a716-446655440000"
    }
  },
  "oneOf": [
    {
      "required": [
        "payload"
      ],
      "not": {
        "required": [
          "template"
        ]
      }
    },
    {
      "required": [
        "template"
      ],
      "not": {
        "required": [
          "payload"
        ]
      }
    }
  ],
  "$defs": {
    "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
    },
    "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
    },
    "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
    },
    "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."
    },
    "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
    },
    "GifVisual": {
      "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
    },
    "ImageVisual": {
      "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
    },
    "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
    },
    "RenderOverrides": {
      "type": "object",
      "additionalProperties": false,
      "description": "Output knobs applied after template/variable resolution.",
      "properties": {
        "name": {
          "type": "string",
          "maxLength": 1000
        },
        "width": {
          "type": "integer",
          "minimum": 1
        },
        "height": {
          "type": "integer",
          "minimum": 1
        },
        "outputFormat": {
          "type": "string",
          "description": "mp4/mov/avi/webm for video; png/jpg/jpeg/webp for image renders."
        },
        "frameRate": {
          "type": "integer",
          "minimum": 1,
          "maximum": 60
        },
        "backgroundColor": {
          "type": "string"
        },
        "snapshotTime": {
          "type": "number",
          "minimum": 0,
          "description": "Image renders only."
        },
        "quality": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "description": "Image renders only (jpg/webp)."
        },
        "transparent": {
          "type": "boolean",
          "description": "Image renders only (png/webp)."
        },
        "resolution": {
          "type": "string",
          "description": "Resolution preset. Explicit width/height overrides force custom unless resolution is also provided."
        }
      }
    },
    "RenderPayload": {
      "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/ImageVisual"
                        },
                        {
                          "$ref": "#/$defs/TextVisual"
                        },
                        {
                          "$ref": "#/$defs/SvgVisual"
                        }
                      ]
                    }
                  },
                  {
                    "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*\\}\\}"
                  }
                ]
              }
            }
          }
        }
      ],
      "x-zvid-source": {
        "schemaVersion": "1.0.0",
        "source": "zvid-integrations/schema/src/zvidSchema.ts via orch/services/zvidSchema.generated.js",
        "sha256": "63b17aa2e6edc73fc25d571df058d372f1e6d51fe39f2b2b1071d9e06add7a3d"
      }
    },
    "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
          }
        }
      }
    },
    "SvgVisual": {
      "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
    },
    "TextVisual": {
      "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"
          ]
        }
      ]
    },
    "VideoVisual": {
      "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
    },
    "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/ImageVisual"
        },
        {
          "$ref": "#/$defs/VideoVisual"
        },
        {
          "$ref": "#/$defs/GifVisual"
        },
        {
          "$ref": "#/$defs/SvgVisual"
        },
        {
          "$ref": "#/$defs/TextVisual"
        }
      ]
    },
    "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
        }
      ]
    }
  }
}
