{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://theformapp.dev/schema/form.schema.json",
  "$comment": "AUTHORING CONTRACT: (1) `questions[].type` is the sole discriminator - given it, every other field of that question is fully determined. (2) Question display order IS the array order; there is no order field. (3) All `id` values must be unique within scope and stable forever - answers reference ids, never labels. (4) Omitted fields with defaults may be left out. (5) Any displayed text (titles, descriptions, confirmation message, page headings) may include {{questionId}} to echo that question's answer (answer piping). (6) For a graded quiz, set settings.scoring.enabled=true and give scored questions a `scoring` object. This document is the entire contract; no other context is needed.",
  "type": "object",
  "properties": {
    "schemaVersion": {
      "default": 1,
      "description": "Schema version. Always 1; whatever is sent is normalised.",
      "type": "number",
      "const": 1
    },
    "title": {
      "type": "string",
      "minLength": 1,
      "maxLength": 300,
      "description": "The form's title, shown at the top and used for the page <title>."
    },
    "description": {
      "description": "Optional intro text shown below the title.",
      "type": "string",
      "maxLength": 4000
    },
    "questions": {
      "minItems": 1,
      "maxItems": 500,
      "type": "array",
      "items": {
        "oneOf": [
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 64,
                "pattern": "^[a-z0-9_-]+$",
                "description": "Stable question id. Unique within the form."
              },
              "title": {
                "type": "string",
                "minLength": 1,
                "maxLength": 2000,
                "description": "The question prompt shown to the respondent."
              },
              "description": {
                "description": "Optional helper text shown beneath the title.",
                "type": "string",
                "maxLength": 2000
              },
              "required": {
                "default": false,
                "description": "If true, the respondent must answer before submitting.",
                "type": "boolean"
              },
              "visibleIf": {
                "description": "Optional conditional logic. When set, the question is shown only if the rule passes; otherwise it is hidden and not validated.",
                "type": "object",
                "properties": {
                  "all": {
                    "description": "Every condition must pass (AND).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  },
                  "any": {
                    "description": "At least one condition must pass (OR).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  }
                },
                "additionalProperties": false
              },
              "scoring": {
                "description": "Optional quiz scoring for this question. Only graded when the form's settings.scoring.enabled is true.",
                "type": "object",
                "properties": {
                  "points": {
                    "type": "number",
                    "description": "Points awarded when the answer is correct."
                  },
                  "correct": {
                    "description": "Accepted correct answer(s). For choice questions use option ids; the answer is correct if it matches (multi-select must match the full set).",
                    "type": "array",
                    "items": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        }
                      ]
                    }
                  }
                },
                "required": [
                  "points"
                ],
                "additionalProperties": false
              },
              "media": {
                "description": "Optional image attached to this question (image-with-question pattern).",
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "maxLength": 2048,
                    "format": "uri",
                    "description": "Image URL. In the builder UI this is always a file from the workspace library."
                  },
                  "alt": {
                    "description": "Accessible alt text.",
                    "type": "string",
                    "maxLength": 200
                  },
                  "position": {
                    "description": "Layout relative to the question (top = default; left/right place image side-by-side with the question).",
                    "type": "string",
                    "enum": [
                      "top",
                      "bottom",
                      "left",
                      "right"
                    ]
                  },
                  "focalX": {
                    "description": "Horizontal focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "focalY": {
                    "description": "Vertical focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "brightness": {
                    "description": "Brightness offset as a percent (-100..100); 0 = unchanged. Useful for hero/cover images sitting under text.",
                    "type": "number",
                    "minimum": -100,
                    "maximum": 100
                  }
                },
                "required": [
                  "url"
                ],
                "additionalProperties": false
              },
              "theme": {
                "description": "Per-slide design override: tbn-* theme tokens that win over the form theme for this block's screen. Absent = inherit the form theme.",
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "type": "string"
                }
              },
              "type": {
                "type": "string",
                "const": "short_text"
              },
              "placeholder": {
                "description": "Greyed-out hint text.",
                "type": "string",
                "maxLength": 200
              },
              "maxLength": {
                "description": "Maximum number of characters allowed.",
                "type": "integer",
                "exclusiveMinimum": 0,
                "maximum": 10000
              }
            },
            "required": [
              "id",
              "title",
              "required",
              "type"
            ],
            "additionalProperties": false,
            "description": "A single-line free-text input. Use for names, short answers."
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 64,
                "pattern": "^[a-z0-9_-]+$",
                "description": "Stable question id. Unique within the form."
              },
              "title": {
                "type": "string",
                "minLength": 1,
                "maxLength": 2000,
                "description": "The question prompt shown to the respondent."
              },
              "description": {
                "description": "Optional helper text shown beneath the title.",
                "type": "string",
                "maxLength": 2000
              },
              "required": {
                "default": false,
                "description": "If true, the respondent must answer before submitting.",
                "type": "boolean"
              },
              "visibleIf": {
                "description": "Optional conditional logic. When set, the question is shown only if the rule passes; otherwise it is hidden and not validated.",
                "type": "object",
                "properties": {
                  "all": {
                    "description": "Every condition must pass (AND).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  },
                  "any": {
                    "description": "At least one condition must pass (OR).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  }
                },
                "additionalProperties": false
              },
              "scoring": {
                "description": "Optional quiz scoring for this question. Only graded when the form's settings.scoring.enabled is true.",
                "type": "object",
                "properties": {
                  "points": {
                    "type": "number",
                    "description": "Points awarded when the answer is correct."
                  },
                  "correct": {
                    "description": "Accepted correct answer(s). For choice questions use option ids; the answer is correct if it matches (multi-select must match the full set).",
                    "type": "array",
                    "items": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        }
                      ]
                    }
                  }
                },
                "required": [
                  "points"
                ],
                "additionalProperties": false
              },
              "media": {
                "description": "Optional image attached to this question (image-with-question pattern).",
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "maxLength": 2048,
                    "format": "uri",
                    "description": "Image URL. In the builder UI this is always a file from the workspace library."
                  },
                  "alt": {
                    "description": "Accessible alt text.",
                    "type": "string",
                    "maxLength": 200
                  },
                  "position": {
                    "description": "Layout relative to the question (top = default; left/right place image side-by-side with the question).",
                    "type": "string",
                    "enum": [
                      "top",
                      "bottom",
                      "left",
                      "right"
                    ]
                  },
                  "focalX": {
                    "description": "Horizontal focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "focalY": {
                    "description": "Vertical focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "brightness": {
                    "description": "Brightness offset as a percent (-100..100); 0 = unchanged. Useful for hero/cover images sitting under text.",
                    "type": "number",
                    "minimum": -100,
                    "maximum": 100
                  }
                },
                "required": [
                  "url"
                ],
                "additionalProperties": false
              },
              "theme": {
                "description": "Per-slide design override: tbn-* theme tokens that win over the form theme for this block's screen. Absent = inherit the form theme.",
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "type": "string"
                }
              },
              "type": {
                "type": "string",
                "const": "long_text"
              },
              "placeholder": {
                "description": "Greyed-out hint text.",
                "type": "string",
                "maxLength": 200
              },
              "maxLength": {
                "description": "Maximum number of characters allowed.",
                "type": "integer",
                "exclusiveMinimum": 0,
                "maximum": 50000
              }
            },
            "required": [
              "id",
              "title",
              "required",
              "type"
            ],
            "additionalProperties": false,
            "description": "A multi-line free-text area. Use for paragraphs, feedback."
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 64,
                "pattern": "^[a-z0-9_-]+$",
                "description": "Stable question id. Unique within the form."
              },
              "title": {
                "type": "string",
                "minLength": 1,
                "maxLength": 2000,
                "description": "The question prompt shown to the respondent."
              },
              "description": {
                "description": "Optional helper text shown beneath the title.",
                "type": "string",
                "maxLength": 2000
              },
              "required": {
                "default": false,
                "description": "If true, the respondent must answer before submitting.",
                "type": "boolean"
              },
              "visibleIf": {
                "description": "Optional conditional logic. When set, the question is shown only if the rule passes; otherwise it is hidden and not validated.",
                "type": "object",
                "properties": {
                  "all": {
                    "description": "Every condition must pass (AND).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  },
                  "any": {
                    "description": "At least one condition must pass (OR).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  }
                },
                "additionalProperties": false
              },
              "scoring": {
                "description": "Optional quiz scoring for this question. Only graded when the form's settings.scoring.enabled is true.",
                "type": "object",
                "properties": {
                  "points": {
                    "type": "number",
                    "description": "Points awarded when the answer is correct."
                  },
                  "correct": {
                    "description": "Accepted correct answer(s). For choice questions use option ids; the answer is correct if it matches (multi-select must match the full set).",
                    "type": "array",
                    "items": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        }
                      ]
                    }
                  }
                },
                "required": [
                  "points"
                ],
                "additionalProperties": false
              },
              "media": {
                "description": "Optional image attached to this question (image-with-question pattern).",
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "maxLength": 2048,
                    "format": "uri",
                    "description": "Image URL. In the builder UI this is always a file from the workspace library."
                  },
                  "alt": {
                    "description": "Accessible alt text.",
                    "type": "string",
                    "maxLength": 200
                  },
                  "position": {
                    "description": "Layout relative to the question (top = default; left/right place image side-by-side with the question).",
                    "type": "string",
                    "enum": [
                      "top",
                      "bottom",
                      "left",
                      "right"
                    ]
                  },
                  "focalX": {
                    "description": "Horizontal focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "focalY": {
                    "description": "Vertical focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "brightness": {
                    "description": "Brightness offset as a percent (-100..100); 0 = unchanged. Useful for hero/cover images sitting under text.",
                    "type": "number",
                    "minimum": -100,
                    "maximum": 100
                  }
                },
                "required": [
                  "url"
                ],
                "additionalProperties": false
              },
              "theme": {
                "description": "Per-slide design override: tbn-* theme tokens that win over the form theme for this block's screen. Absent = inherit the form theme.",
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "type": "string"
                }
              },
              "type": {
                "type": "string",
                "const": "single_select"
              },
              "options": {
                "default": [],
                "description": "The choices. The respondent picks exactly one. May be empty when `optionsConnector` is set - at fill time, options are loaded from the connector.",
                "maxItems": 200,
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 64,
                      "pattern": "^[a-z0-9_-]+$",
                      "description": "Stable option id. Answers store this id, not the label, so labels can be edited freely later."
                    },
                    "label": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 500,
                      "description": "Human-readable text shown next to the choice."
                    }
                  },
                  "required": [
                    "id",
                    "label"
                  ],
                  "additionalProperties": false,
                  "description": "A selectable choice for select/dropdown questions."
                }
              },
              "optionsConnector": {
                "type": "object",
                "properties": {
                  "connectorId": {
                    "type": "string",
                    "minLength": 1,
                    "description": "Must match a Connector id at the form's `connectors[]` array. Cross-checked in superRefine."
                  },
                  "refreshOn": {
                    "description": "When ANY of these question ids change value, re-invoke the connector. Used for cascading dropdowns (country → state → city).",
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1
                    }
                  }
                },
                "required": [
                  "connectorId"
                ],
                "additionalProperties": false,
                "description": "Wires a select-type question's options to a connector instead of (or in addition to) a static list."
              },
              "layout": {
                "default": "default",
                "description": "How the options are rendered. `default` = vertical radio rows (best for 5+ options or long labels). `pills` = horizontal flex-wrap of rounded buttons (best for 2-8 short options, mobile-friendly).",
                "type": "string",
                "enum": [
                  "default",
                  "pills"
                ]
              },
              "allowOther": {
                "default": false,
                "description": "If true, adds an 'Other' choice with a free-text field.",
                "type": "boolean"
              }
            },
            "required": [
              "id",
              "title",
              "required",
              "type",
              "options",
              "layout",
              "allowOther"
            ],
            "additionalProperties": false,
            "description": "Pick exactly one option. Rendered as radio buttons (default) or pills. This is the classic 'single answer' question."
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 64,
                "pattern": "^[a-z0-9_-]+$",
                "description": "Stable question id. Unique within the form."
              },
              "title": {
                "type": "string",
                "minLength": 1,
                "maxLength": 2000,
                "description": "The question prompt shown to the respondent."
              },
              "description": {
                "description": "Optional helper text shown beneath the title.",
                "type": "string",
                "maxLength": 2000
              },
              "required": {
                "default": false,
                "description": "If true, the respondent must answer before submitting.",
                "type": "boolean"
              },
              "visibleIf": {
                "description": "Optional conditional logic. When set, the question is shown only if the rule passes; otherwise it is hidden and not validated.",
                "type": "object",
                "properties": {
                  "all": {
                    "description": "Every condition must pass (AND).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  },
                  "any": {
                    "description": "At least one condition must pass (OR).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  }
                },
                "additionalProperties": false
              },
              "scoring": {
                "description": "Optional quiz scoring for this question. Only graded when the form's settings.scoring.enabled is true.",
                "type": "object",
                "properties": {
                  "points": {
                    "type": "number",
                    "description": "Points awarded when the answer is correct."
                  },
                  "correct": {
                    "description": "Accepted correct answer(s). For choice questions use option ids; the answer is correct if it matches (multi-select must match the full set).",
                    "type": "array",
                    "items": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        }
                      ]
                    }
                  }
                },
                "required": [
                  "points"
                ],
                "additionalProperties": false
              },
              "media": {
                "description": "Optional image attached to this question (image-with-question pattern).",
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "maxLength": 2048,
                    "format": "uri",
                    "description": "Image URL. In the builder UI this is always a file from the workspace library."
                  },
                  "alt": {
                    "description": "Accessible alt text.",
                    "type": "string",
                    "maxLength": 200
                  },
                  "position": {
                    "description": "Layout relative to the question (top = default; left/right place image side-by-side with the question).",
                    "type": "string",
                    "enum": [
                      "top",
                      "bottom",
                      "left",
                      "right"
                    ]
                  },
                  "focalX": {
                    "description": "Horizontal focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "focalY": {
                    "description": "Vertical focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "brightness": {
                    "description": "Brightness offset as a percent (-100..100); 0 = unchanged. Useful for hero/cover images sitting under text.",
                    "type": "number",
                    "minimum": -100,
                    "maximum": 100
                  }
                },
                "required": [
                  "url"
                ],
                "additionalProperties": false
              },
              "theme": {
                "description": "Per-slide design override: tbn-* theme tokens that win over the form theme for this block's screen. Absent = inherit the form theme.",
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "type": "string"
                }
              },
              "type": {
                "type": "string",
                "const": "multi_select"
              },
              "options": {
                "default": [],
                "description": "The choices. The respondent may pick several. May be empty when `optionsConnector` is set.",
                "maxItems": 200,
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 64,
                      "pattern": "^[a-z0-9_-]+$",
                      "description": "Stable option id. Answers store this id, not the label, so labels can be edited freely later."
                    },
                    "label": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 500,
                      "description": "Human-readable text shown next to the choice."
                    }
                  },
                  "required": [
                    "id",
                    "label"
                  ],
                  "additionalProperties": false,
                  "description": "A selectable choice for select/dropdown questions."
                }
              },
              "optionsConnector": {
                "type": "object",
                "properties": {
                  "connectorId": {
                    "type": "string",
                    "minLength": 1,
                    "description": "Must match a Connector id at the form's `connectors[]` array. Cross-checked in superRefine."
                  },
                  "refreshOn": {
                    "description": "When ANY of these question ids change value, re-invoke the connector. Used for cascading dropdowns (country → state → city).",
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1
                    }
                  }
                },
                "required": [
                  "connectorId"
                ],
                "additionalProperties": false,
                "description": "Wires a select-type question's options to a connector instead of (or in addition to) a static list."
              },
              "layout": {
                "default": "default",
                "description": "How the options are rendered. `default` = vertical checkbox rows. `pills` = horizontal flex-wrap of toggleable rounded buttons (mobile-friendly; best for 2-12 short labels).",
                "type": "string",
                "enum": [
                  "default",
                  "pills"
                ]
              },
              "minSelections": {
                "description": "Minimum number of options that must be selected.",
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              },
              "maxSelections": {
                "description": "Maximum number of options that may be selected.",
                "type": "integer",
                "exclusiveMinimum": 0,
                "maximum": 9007199254740991
              },
              "allowOther": {
                "default": false,
                "description": "If true, adds an 'Other' choice with a free-text field.",
                "type": "boolean"
              }
            },
            "required": [
              "id",
              "title",
              "required",
              "type",
              "options",
              "layout",
              "allowOther"
            ],
            "additionalProperties": false,
            "description": "Pick one or more options. Rendered as checkboxes. This is the 'multi answer' question."
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 64,
                "pattern": "^[a-z0-9_-]+$",
                "description": "Stable question id. Unique within the form."
              },
              "title": {
                "type": "string",
                "minLength": 1,
                "maxLength": 2000,
                "description": "The question prompt shown to the respondent."
              },
              "description": {
                "description": "Optional helper text shown beneath the title.",
                "type": "string",
                "maxLength": 2000
              },
              "required": {
                "default": false,
                "description": "If true, the respondent must answer before submitting.",
                "type": "boolean"
              },
              "visibleIf": {
                "description": "Optional conditional logic. When set, the question is shown only if the rule passes; otherwise it is hidden and not validated.",
                "type": "object",
                "properties": {
                  "all": {
                    "description": "Every condition must pass (AND).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  },
                  "any": {
                    "description": "At least one condition must pass (OR).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  }
                },
                "additionalProperties": false
              },
              "scoring": {
                "description": "Optional quiz scoring for this question. Only graded when the form's settings.scoring.enabled is true.",
                "type": "object",
                "properties": {
                  "points": {
                    "type": "number",
                    "description": "Points awarded when the answer is correct."
                  },
                  "correct": {
                    "description": "Accepted correct answer(s). For choice questions use option ids; the answer is correct if it matches (multi-select must match the full set).",
                    "type": "array",
                    "items": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        }
                      ]
                    }
                  }
                },
                "required": [
                  "points"
                ],
                "additionalProperties": false
              },
              "media": {
                "description": "Optional image attached to this question (image-with-question pattern).",
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "maxLength": 2048,
                    "format": "uri",
                    "description": "Image URL. In the builder UI this is always a file from the workspace library."
                  },
                  "alt": {
                    "description": "Accessible alt text.",
                    "type": "string",
                    "maxLength": 200
                  },
                  "position": {
                    "description": "Layout relative to the question (top = default; left/right place image side-by-side with the question).",
                    "type": "string",
                    "enum": [
                      "top",
                      "bottom",
                      "left",
                      "right"
                    ]
                  },
                  "focalX": {
                    "description": "Horizontal focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "focalY": {
                    "description": "Vertical focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "brightness": {
                    "description": "Brightness offset as a percent (-100..100); 0 = unchanged. Useful for hero/cover images sitting under text.",
                    "type": "number",
                    "minimum": -100,
                    "maximum": 100
                  }
                },
                "required": [
                  "url"
                ],
                "additionalProperties": false
              },
              "theme": {
                "description": "Per-slide design override: tbn-* theme tokens that win over the form theme for this block's screen. Absent = inherit the form theme.",
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "type": "string"
                }
              },
              "type": {
                "type": "string",
                "const": "dropdown"
              },
              "options": {
                "default": [],
                "description": "The choices, shown in a collapsed menu. May be empty when `optionsConnector` is set - at fill time, options are loaded from the connector (cascading dropdowns).",
                "maxItems": 1000,
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 64,
                      "pattern": "^[a-z0-9_-]+$",
                      "description": "Stable option id. Answers store this id, not the label, so labels can be edited freely later."
                    },
                    "label": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 500,
                      "description": "Human-readable text shown next to the choice."
                    }
                  },
                  "required": [
                    "id",
                    "label"
                  ],
                  "additionalProperties": false,
                  "description": "A selectable choice for select/dropdown questions."
                }
              },
              "optionsConnector": {
                "type": "object",
                "properties": {
                  "connectorId": {
                    "type": "string",
                    "minLength": 1,
                    "description": "Must match a Connector id at the form's `connectors[]` array. Cross-checked in superRefine."
                  },
                  "refreshOn": {
                    "description": "When ANY of these question ids change value, re-invoke the connector. Used for cascading dropdowns (country → state → city).",
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1
                    }
                  }
                },
                "required": [
                  "connectorId"
                ],
                "additionalProperties": false,
                "description": "Wires a select-type question's options to a connector instead of (or in addition to) a static list."
              },
              "placeholder": {
                "description": "Text shown before a choice is made (e.g. 'Select one…').",
                "type": "string",
                "maxLength": 200
              },
              "allowOther": {
                "default": false,
                "description": "If true, adds an 'Other…' choice with a free-text field.",
                "type": "boolean"
              }
            },
            "required": [
              "id",
              "title",
              "required",
              "type",
              "options",
              "allowOther"
            ],
            "additionalProperties": false,
            "description": "Pick exactly one option from a collapsed menu. Prefer over single_select when there are many options."
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 64,
                "pattern": "^[a-z0-9_-]+$",
                "description": "Stable question id. Unique within the form."
              },
              "title": {
                "type": "string",
                "minLength": 1,
                "maxLength": 2000,
                "description": "The question prompt shown to the respondent."
              },
              "description": {
                "description": "Optional helper text shown beneath the title.",
                "type": "string",
                "maxLength": 2000
              },
              "required": {
                "default": false,
                "description": "If true, the respondent must answer before submitting.",
                "type": "boolean"
              },
              "visibleIf": {
                "description": "Optional conditional logic. When set, the question is shown only if the rule passes; otherwise it is hidden and not validated.",
                "type": "object",
                "properties": {
                  "all": {
                    "description": "Every condition must pass (AND).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  },
                  "any": {
                    "description": "At least one condition must pass (OR).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  }
                },
                "additionalProperties": false
              },
              "scoring": {
                "description": "Optional quiz scoring for this question. Only graded when the form's settings.scoring.enabled is true.",
                "type": "object",
                "properties": {
                  "points": {
                    "type": "number",
                    "description": "Points awarded when the answer is correct."
                  },
                  "correct": {
                    "description": "Accepted correct answer(s). For choice questions use option ids; the answer is correct if it matches (multi-select must match the full set).",
                    "type": "array",
                    "items": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        }
                      ]
                    }
                  }
                },
                "required": [
                  "points"
                ],
                "additionalProperties": false
              },
              "media": {
                "description": "Optional image attached to this question (image-with-question pattern).",
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "maxLength": 2048,
                    "format": "uri",
                    "description": "Image URL. In the builder UI this is always a file from the workspace library."
                  },
                  "alt": {
                    "description": "Accessible alt text.",
                    "type": "string",
                    "maxLength": 200
                  },
                  "position": {
                    "description": "Layout relative to the question (top = default; left/right place image side-by-side with the question).",
                    "type": "string",
                    "enum": [
                      "top",
                      "bottom",
                      "left",
                      "right"
                    ]
                  },
                  "focalX": {
                    "description": "Horizontal focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "focalY": {
                    "description": "Vertical focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "brightness": {
                    "description": "Brightness offset as a percent (-100..100); 0 = unchanged. Useful for hero/cover images sitting under text.",
                    "type": "number",
                    "minimum": -100,
                    "maximum": 100
                  }
                },
                "required": [
                  "url"
                ],
                "additionalProperties": false
              },
              "theme": {
                "description": "Per-slide design override: tbn-* theme tokens that win over the form theme for this block's screen. Absent = inherit the form theme.",
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "type": "string"
                }
              },
              "type": {
                "type": "string",
                "const": "number"
              },
              "min": {
                "description": "Smallest accepted value.",
                "type": "number"
              },
              "max": {
                "description": "Largest accepted value.",
                "type": "number"
              },
              "step": {
                "description": "Allowed increment.",
                "type": "number",
                "exclusiveMinimum": 0
              },
              "placeholder": {
                "type": "string",
                "maxLength": 200
              }
            },
            "required": [
              "id",
              "title",
              "required",
              "type"
            ],
            "additionalProperties": false,
            "description": "A numeric input."
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 64,
                "pattern": "^[a-z0-9_-]+$",
                "description": "Stable question id. Unique within the form."
              },
              "title": {
                "type": "string",
                "minLength": 1,
                "maxLength": 2000,
                "description": "The question prompt shown to the respondent."
              },
              "description": {
                "description": "Optional helper text shown beneath the title.",
                "type": "string",
                "maxLength": 2000
              },
              "required": {
                "default": false,
                "description": "If true, the respondent must answer before submitting.",
                "type": "boolean"
              },
              "visibleIf": {
                "description": "Optional conditional logic. When set, the question is shown only if the rule passes; otherwise it is hidden and not validated.",
                "type": "object",
                "properties": {
                  "all": {
                    "description": "Every condition must pass (AND).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  },
                  "any": {
                    "description": "At least one condition must pass (OR).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  }
                },
                "additionalProperties": false
              },
              "scoring": {
                "description": "Optional quiz scoring for this question. Only graded when the form's settings.scoring.enabled is true.",
                "type": "object",
                "properties": {
                  "points": {
                    "type": "number",
                    "description": "Points awarded when the answer is correct."
                  },
                  "correct": {
                    "description": "Accepted correct answer(s). For choice questions use option ids; the answer is correct if it matches (multi-select must match the full set).",
                    "type": "array",
                    "items": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        }
                      ]
                    }
                  }
                },
                "required": [
                  "points"
                ],
                "additionalProperties": false
              },
              "media": {
                "description": "Optional image attached to this question (image-with-question pattern).",
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "maxLength": 2048,
                    "format": "uri",
                    "description": "Image URL. In the builder UI this is always a file from the workspace library."
                  },
                  "alt": {
                    "description": "Accessible alt text.",
                    "type": "string",
                    "maxLength": 200
                  },
                  "position": {
                    "description": "Layout relative to the question (top = default; left/right place image side-by-side with the question).",
                    "type": "string",
                    "enum": [
                      "top",
                      "bottom",
                      "left",
                      "right"
                    ]
                  },
                  "focalX": {
                    "description": "Horizontal focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "focalY": {
                    "description": "Vertical focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "brightness": {
                    "description": "Brightness offset as a percent (-100..100); 0 = unchanged. Useful for hero/cover images sitting under text.",
                    "type": "number",
                    "minimum": -100,
                    "maximum": 100
                  }
                },
                "required": [
                  "url"
                ],
                "additionalProperties": false
              },
              "theme": {
                "description": "Per-slide design override: tbn-* theme tokens that win over the form theme for this block's screen. Absent = inherit the form theme.",
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "type": "string"
                }
              },
              "type": {
                "type": "string",
                "const": "email"
              },
              "placeholder": {
                "type": "string",
                "maxLength": 200
              }
            },
            "required": [
              "id",
              "title",
              "required",
              "type"
            ],
            "additionalProperties": false,
            "description": "An email-address input, validated for email format."
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 64,
                "pattern": "^[a-z0-9_-]+$",
                "description": "Stable question id. Unique within the form."
              },
              "title": {
                "type": "string",
                "minLength": 1,
                "maxLength": 2000,
                "description": "The question prompt shown to the respondent."
              },
              "description": {
                "description": "Optional helper text shown beneath the title.",
                "type": "string",
                "maxLength": 2000
              },
              "required": {
                "default": false,
                "description": "If true, the respondent must answer before submitting.",
                "type": "boolean"
              },
              "visibleIf": {
                "description": "Optional conditional logic. When set, the question is shown only if the rule passes; otherwise it is hidden and not validated.",
                "type": "object",
                "properties": {
                  "all": {
                    "description": "Every condition must pass (AND).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  },
                  "any": {
                    "description": "At least one condition must pass (OR).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  }
                },
                "additionalProperties": false
              },
              "scoring": {
                "description": "Optional quiz scoring for this question. Only graded when the form's settings.scoring.enabled is true.",
                "type": "object",
                "properties": {
                  "points": {
                    "type": "number",
                    "description": "Points awarded when the answer is correct."
                  },
                  "correct": {
                    "description": "Accepted correct answer(s). For choice questions use option ids; the answer is correct if it matches (multi-select must match the full set).",
                    "type": "array",
                    "items": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        }
                      ]
                    }
                  }
                },
                "required": [
                  "points"
                ],
                "additionalProperties": false
              },
              "media": {
                "description": "Optional image attached to this question (image-with-question pattern).",
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "maxLength": 2048,
                    "format": "uri",
                    "description": "Image URL. In the builder UI this is always a file from the workspace library."
                  },
                  "alt": {
                    "description": "Accessible alt text.",
                    "type": "string",
                    "maxLength": 200
                  },
                  "position": {
                    "description": "Layout relative to the question (top = default; left/right place image side-by-side with the question).",
                    "type": "string",
                    "enum": [
                      "top",
                      "bottom",
                      "left",
                      "right"
                    ]
                  },
                  "focalX": {
                    "description": "Horizontal focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "focalY": {
                    "description": "Vertical focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "brightness": {
                    "description": "Brightness offset as a percent (-100..100); 0 = unchanged. Useful for hero/cover images sitting under text.",
                    "type": "number",
                    "minimum": -100,
                    "maximum": 100
                  }
                },
                "required": [
                  "url"
                ],
                "additionalProperties": false
              },
              "theme": {
                "description": "Per-slide design override: tbn-* theme tokens that win over the form theme for this block's screen. Absent = inherit the form theme.",
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "type": "string"
                }
              },
              "type": {
                "type": "string",
                "const": "date"
              },
              "min": {
                "description": "Earliest accepted date, ISO 8601 (YYYY-MM-DD).",
                "type": "string",
                "format": "date",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
              },
              "max": {
                "description": "Latest accepted date, ISO 8601 (YYYY-MM-DD).",
                "type": "string",
                "format": "date",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
              }
            },
            "required": [
              "id",
              "title",
              "required",
              "type"
            ],
            "additionalProperties": false,
            "description": "A calendar date input."
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 64,
                "pattern": "^[a-z0-9_-]+$",
                "description": "Stable question id. Unique within the form."
              },
              "title": {
                "type": "string",
                "minLength": 1,
                "maxLength": 2000,
                "description": "The question prompt shown to the respondent."
              },
              "description": {
                "description": "Optional helper text shown beneath the title.",
                "type": "string",
                "maxLength": 2000
              },
              "required": {
                "default": false,
                "description": "If true, the respondent must answer before submitting.",
                "type": "boolean"
              },
              "visibleIf": {
                "description": "Optional conditional logic. When set, the question is shown only if the rule passes; otherwise it is hidden and not validated.",
                "type": "object",
                "properties": {
                  "all": {
                    "description": "Every condition must pass (AND).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  },
                  "any": {
                    "description": "At least one condition must pass (OR).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  }
                },
                "additionalProperties": false
              },
              "scoring": {
                "description": "Optional quiz scoring for this question. Only graded when the form's settings.scoring.enabled is true.",
                "type": "object",
                "properties": {
                  "points": {
                    "type": "number",
                    "description": "Points awarded when the answer is correct."
                  },
                  "correct": {
                    "description": "Accepted correct answer(s). For choice questions use option ids; the answer is correct if it matches (multi-select must match the full set).",
                    "type": "array",
                    "items": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        }
                      ]
                    }
                  }
                },
                "required": [
                  "points"
                ],
                "additionalProperties": false
              },
              "media": {
                "description": "Optional image attached to this question (image-with-question pattern).",
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "maxLength": 2048,
                    "format": "uri",
                    "description": "Image URL. In the builder UI this is always a file from the workspace library."
                  },
                  "alt": {
                    "description": "Accessible alt text.",
                    "type": "string",
                    "maxLength": 200
                  },
                  "position": {
                    "description": "Layout relative to the question (top = default; left/right place image side-by-side with the question).",
                    "type": "string",
                    "enum": [
                      "top",
                      "bottom",
                      "left",
                      "right"
                    ]
                  },
                  "focalX": {
                    "description": "Horizontal focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "focalY": {
                    "description": "Vertical focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "brightness": {
                    "description": "Brightness offset as a percent (-100..100); 0 = unchanged. Useful for hero/cover images sitting under text.",
                    "type": "number",
                    "minimum": -100,
                    "maximum": 100
                  }
                },
                "required": [
                  "url"
                ],
                "additionalProperties": false
              },
              "theme": {
                "description": "Per-slide design override: tbn-* theme tokens that win over the form theme for this block's screen. Absent = inherit the form theme.",
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "type": "string"
                }
              },
              "type": {
                "type": "string",
                "const": "rating"
              },
              "max": {
                "default": 5,
                "description": "Highest rating value (scale is 1..max).",
                "type": "integer",
                "minimum": 2,
                "maximum": 10
              },
              "style": {
                "default": "star",
                "description": "Render as stars or as numbered buttons.",
                "type": "string",
                "enum": [
                  "star",
                  "number"
                ]
              }
            },
            "required": [
              "id",
              "title",
              "required",
              "type",
              "max",
              "style"
            ],
            "additionalProperties": false,
            "description": "A 1..max rating scale."
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 64,
                "pattern": "^[a-z0-9_-]+$",
                "description": "Stable question id. Unique within the form."
              },
              "title": {
                "type": "string",
                "minLength": 1,
                "maxLength": 2000,
                "description": "The question prompt shown to the respondent."
              },
              "description": {
                "description": "Optional helper text shown beneath the title.",
                "type": "string",
                "maxLength": 2000
              },
              "required": {
                "default": false,
                "description": "If true, the respondent must answer before submitting.",
                "type": "boolean"
              },
              "visibleIf": {
                "description": "Optional conditional logic. When set, the question is shown only if the rule passes; otherwise it is hidden and not validated.",
                "type": "object",
                "properties": {
                  "all": {
                    "description": "Every condition must pass (AND).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  },
                  "any": {
                    "description": "At least one condition must pass (OR).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  }
                },
                "additionalProperties": false
              },
              "scoring": {
                "description": "Optional quiz scoring for this question. Only graded when the form's settings.scoring.enabled is true.",
                "type": "object",
                "properties": {
                  "points": {
                    "type": "number",
                    "description": "Points awarded when the answer is correct."
                  },
                  "correct": {
                    "description": "Accepted correct answer(s). For choice questions use option ids; the answer is correct if it matches (multi-select must match the full set).",
                    "type": "array",
                    "items": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        }
                      ]
                    }
                  }
                },
                "required": [
                  "points"
                ],
                "additionalProperties": false
              },
              "media": {
                "description": "Optional image attached to this question (image-with-question pattern).",
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "maxLength": 2048,
                    "format": "uri",
                    "description": "Image URL. In the builder UI this is always a file from the workspace library."
                  },
                  "alt": {
                    "description": "Accessible alt text.",
                    "type": "string",
                    "maxLength": 200
                  },
                  "position": {
                    "description": "Layout relative to the question (top = default; left/right place image side-by-side with the question).",
                    "type": "string",
                    "enum": [
                      "top",
                      "bottom",
                      "left",
                      "right"
                    ]
                  },
                  "focalX": {
                    "description": "Horizontal focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "focalY": {
                    "description": "Vertical focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "brightness": {
                    "description": "Brightness offset as a percent (-100..100); 0 = unchanged. Useful for hero/cover images sitting under text.",
                    "type": "number",
                    "minimum": -100,
                    "maximum": 100
                  }
                },
                "required": [
                  "url"
                ],
                "additionalProperties": false
              },
              "theme": {
                "description": "Per-slide design override: tbn-* theme tokens that win over the form theme for this block's screen. Absent = inherit the form theme.",
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "type": "string"
                }
              },
              "type": {
                "type": "string",
                "const": "phone"
              },
              "placeholder": {
                "type": "string",
                "maxLength": 200
              }
            },
            "required": [
              "id",
              "title",
              "required",
              "type"
            ],
            "additionalProperties": false,
            "description": "A telephone-number input."
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 64,
                "pattern": "^[a-z0-9_-]+$",
                "description": "Stable question id. Unique within the form."
              },
              "title": {
                "type": "string",
                "minLength": 1,
                "maxLength": 2000,
                "description": "The question prompt shown to the respondent."
              },
              "description": {
                "description": "Optional helper text shown beneath the title.",
                "type": "string",
                "maxLength": 2000
              },
              "required": {
                "default": false,
                "description": "If true, the respondent must answer before submitting.",
                "type": "boolean"
              },
              "visibleIf": {
                "description": "Optional conditional logic. When set, the question is shown only if the rule passes; otherwise it is hidden and not validated.",
                "type": "object",
                "properties": {
                  "all": {
                    "description": "Every condition must pass (AND).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  },
                  "any": {
                    "description": "At least one condition must pass (OR).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  }
                },
                "additionalProperties": false
              },
              "scoring": {
                "description": "Optional quiz scoring for this question. Only graded when the form's settings.scoring.enabled is true.",
                "type": "object",
                "properties": {
                  "points": {
                    "type": "number",
                    "description": "Points awarded when the answer is correct."
                  },
                  "correct": {
                    "description": "Accepted correct answer(s). For choice questions use option ids; the answer is correct if it matches (multi-select must match the full set).",
                    "type": "array",
                    "items": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        }
                      ]
                    }
                  }
                },
                "required": [
                  "points"
                ],
                "additionalProperties": false
              },
              "media": {
                "description": "Optional image attached to this question (image-with-question pattern).",
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "maxLength": 2048,
                    "format": "uri",
                    "description": "Image URL. In the builder UI this is always a file from the workspace library."
                  },
                  "alt": {
                    "description": "Accessible alt text.",
                    "type": "string",
                    "maxLength": 200
                  },
                  "position": {
                    "description": "Layout relative to the question (top = default; left/right place image side-by-side with the question).",
                    "type": "string",
                    "enum": [
                      "top",
                      "bottom",
                      "left",
                      "right"
                    ]
                  },
                  "focalX": {
                    "description": "Horizontal focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "focalY": {
                    "description": "Vertical focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "brightness": {
                    "description": "Brightness offset as a percent (-100..100); 0 = unchanged. Useful for hero/cover images sitting under text.",
                    "type": "number",
                    "minimum": -100,
                    "maximum": 100
                  }
                },
                "required": [
                  "url"
                ],
                "additionalProperties": false
              },
              "theme": {
                "description": "Per-slide design override: tbn-* theme tokens that win over the form theme for this block's screen. Absent = inherit the form theme.",
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "type": "string"
                }
              },
              "type": {
                "type": "string",
                "const": "name"
              },
              "includeMiddle": {
                "default": false,
                "description": "Also collect a middle name.",
                "type": "boolean"
              }
            },
            "required": [
              "id",
              "title",
              "required",
              "type",
              "includeMiddle"
            ],
            "additionalProperties": false,
            "description": "A person's name: first and last (optionally middle)."
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 64,
                "pattern": "^[a-z0-9_-]+$",
                "description": "Stable question id. Unique within the form."
              },
              "title": {
                "type": "string",
                "minLength": 1,
                "maxLength": 2000,
                "description": "The question prompt shown to the respondent."
              },
              "description": {
                "description": "Optional helper text shown beneath the title.",
                "type": "string",
                "maxLength": 2000
              },
              "required": {
                "default": false,
                "description": "If true, the respondent must answer before submitting.",
                "type": "boolean"
              },
              "visibleIf": {
                "description": "Optional conditional logic. When set, the question is shown only if the rule passes; otherwise it is hidden and not validated.",
                "type": "object",
                "properties": {
                  "all": {
                    "description": "Every condition must pass (AND).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  },
                  "any": {
                    "description": "At least one condition must pass (OR).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  }
                },
                "additionalProperties": false
              },
              "scoring": {
                "description": "Optional quiz scoring for this question. Only graded when the form's settings.scoring.enabled is true.",
                "type": "object",
                "properties": {
                  "points": {
                    "type": "number",
                    "description": "Points awarded when the answer is correct."
                  },
                  "correct": {
                    "description": "Accepted correct answer(s). For choice questions use option ids; the answer is correct if it matches (multi-select must match the full set).",
                    "type": "array",
                    "items": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        }
                      ]
                    }
                  }
                },
                "required": [
                  "points"
                ],
                "additionalProperties": false
              },
              "media": {
                "description": "Optional image attached to this question (image-with-question pattern).",
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "maxLength": 2048,
                    "format": "uri",
                    "description": "Image URL. In the builder UI this is always a file from the workspace library."
                  },
                  "alt": {
                    "description": "Accessible alt text.",
                    "type": "string",
                    "maxLength": 200
                  },
                  "position": {
                    "description": "Layout relative to the question (top = default; left/right place image side-by-side with the question).",
                    "type": "string",
                    "enum": [
                      "top",
                      "bottom",
                      "left",
                      "right"
                    ]
                  },
                  "focalX": {
                    "description": "Horizontal focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "focalY": {
                    "description": "Vertical focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "brightness": {
                    "description": "Brightness offset as a percent (-100..100); 0 = unchanged. Useful for hero/cover images sitting under text.",
                    "type": "number",
                    "minimum": -100,
                    "maximum": 100
                  }
                },
                "required": [
                  "url"
                ],
                "additionalProperties": false
              },
              "theme": {
                "description": "Per-slide design override: tbn-* theme tokens that win over the form theme for this block's screen. Absent = inherit the form theme.",
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "type": "string"
                }
              },
              "type": {
                "type": "string",
                "const": "address"
              },
              "includeCountry": {
                "default": true,
                "description": "Include a country field.",
                "type": "boolean"
              }
            },
            "required": [
              "id",
              "title",
              "required",
              "type",
              "includeCountry"
            ],
            "additionalProperties": false,
            "description": "A postal address (line 1/2, city, state/region, postal code, optional country)."
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 64,
                "pattern": "^[a-z0-9_-]+$",
                "description": "Stable question id. Unique within the form."
              },
              "title": {
                "type": "string",
                "minLength": 1,
                "maxLength": 2000,
                "description": "The question prompt shown to the respondent."
              },
              "description": {
                "description": "Optional helper text shown beneath the title.",
                "type": "string",
                "maxLength": 2000
              },
              "required": {
                "default": false,
                "description": "If true, the respondent must answer before submitting.",
                "type": "boolean"
              },
              "visibleIf": {
                "description": "Optional conditional logic. When set, the question is shown only if the rule passes; otherwise it is hidden and not validated.",
                "type": "object",
                "properties": {
                  "all": {
                    "description": "Every condition must pass (AND).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  },
                  "any": {
                    "description": "At least one condition must pass (OR).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  }
                },
                "additionalProperties": false
              },
              "scoring": {
                "description": "Optional quiz scoring for this question. Only graded when the form's settings.scoring.enabled is true.",
                "type": "object",
                "properties": {
                  "points": {
                    "type": "number",
                    "description": "Points awarded when the answer is correct."
                  },
                  "correct": {
                    "description": "Accepted correct answer(s). For choice questions use option ids; the answer is correct if it matches (multi-select must match the full set).",
                    "type": "array",
                    "items": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        }
                      ]
                    }
                  }
                },
                "required": [
                  "points"
                ],
                "additionalProperties": false
              },
              "media": {
                "description": "Optional image attached to this question (image-with-question pattern).",
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "maxLength": 2048,
                    "format": "uri",
                    "description": "Image URL. In the builder UI this is always a file from the workspace library."
                  },
                  "alt": {
                    "description": "Accessible alt text.",
                    "type": "string",
                    "maxLength": 200
                  },
                  "position": {
                    "description": "Layout relative to the question (top = default; left/right place image side-by-side with the question).",
                    "type": "string",
                    "enum": [
                      "top",
                      "bottom",
                      "left",
                      "right"
                    ]
                  },
                  "focalX": {
                    "description": "Horizontal focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "focalY": {
                    "description": "Vertical focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "brightness": {
                    "description": "Brightness offset as a percent (-100..100); 0 = unchanged. Useful for hero/cover images sitting under text.",
                    "type": "number",
                    "minimum": -100,
                    "maximum": 100
                  }
                },
                "required": [
                  "url"
                ],
                "additionalProperties": false
              },
              "theme": {
                "description": "Per-slide design override: tbn-* theme tokens that win over the form theme for this block's screen. Absent = inherit the form theme.",
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "type": "string"
                }
              },
              "type": {
                "type": "string",
                "const": "file_upload"
              },
              "maxFiles": {
                "default": 1,
                "description": "Maximum number of files the respondent may attach.",
                "type": "integer",
                "minimum": 1,
                "maximum": 20
              },
              "maxSizeMb": {
                "default": 10,
                "description": "Maximum size per file, in megabytes.",
                "type": "integer",
                "minimum": 1,
                "maximum": 100
              },
              "accept": {
                "description": "Allowed extensions e.g. ['pdf','png']. Omit to allow any.",
                "maxItems": 30,
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            },
            "required": [
              "id",
              "title",
              "required",
              "type",
              "maxFiles",
              "maxSizeMb"
            ],
            "additionalProperties": false,
            "description": "Lets the respondent upload one or more files."
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 64,
                "pattern": "^[a-z0-9_-]+$",
                "description": "Stable question id. Unique within the form."
              },
              "title": {
                "type": "string",
                "minLength": 1,
                "maxLength": 2000,
                "description": "The question prompt shown to the respondent."
              },
              "description": {
                "description": "Optional helper text shown beneath the title.",
                "type": "string",
                "maxLength": 2000
              },
              "required": {
                "default": false,
                "description": "If true, the respondent must answer before submitting.",
                "type": "boolean"
              },
              "visibleIf": {
                "description": "Optional conditional logic. When set, the question is shown only if the rule passes; otherwise it is hidden and not validated.",
                "type": "object",
                "properties": {
                  "all": {
                    "description": "Every condition must pass (AND).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  },
                  "any": {
                    "description": "At least one condition must pass (OR).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  }
                },
                "additionalProperties": false
              },
              "scoring": {
                "description": "Optional quiz scoring for this question. Only graded when the form's settings.scoring.enabled is true.",
                "type": "object",
                "properties": {
                  "points": {
                    "type": "number",
                    "description": "Points awarded when the answer is correct."
                  },
                  "correct": {
                    "description": "Accepted correct answer(s). For choice questions use option ids; the answer is correct if it matches (multi-select must match the full set).",
                    "type": "array",
                    "items": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        }
                      ]
                    }
                  }
                },
                "required": [
                  "points"
                ],
                "additionalProperties": false
              },
              "media": {
                "description": "Optional image attached to this question (image-with-question pattern).",
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "maxLength": 2048,
                    "format": "uri",
                    "description": "Image URL. In the builder UI this is always a file from the workspace library."
                  },
                  "alt": {
                    "description": "Accessible alt text.",
                    "type": "string",
                    "maxLength": 200
                  },
                  "position": {
                    "description": "Layout relative to the question (top = default; left/right place image side-by-side with the question).",
                    "type": "string",
                    "enum": [
                      "top",
                      "bottom",
                      "left",
                      "right"
                    ]
                  },
                  "focalX": {
                    "description": "Horizontal focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "focalY": {
                    "description": "Vertical focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "brightness": {
                    "description": "Brightness offset as a percent (-100..100); 0 = unchanged. Useful for hero/cover images sitting under text.",
                    "type": "number",
                    "minimum": -100,
                    "maximum": 100
                  }
                },
                "required": [
                  "url"
                ],
                "additionalProperties": false
              },
              "theme": {
                "description": "Per-slide design override: tbn-* theme tokens that win over the form theme for this block's screen. Absent = inherit the form theme.",
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "type": "string"
                }
              },
              "type": {
                "type": "string",
                "const": "scale"
              },
              "min": {
                "default": 1,
                "description": "Lowest value on the scale (0 or 1).",
                "type": "integer",
                "minimum": 0,
                "maximum": 1
              },
              "max": {
                "default": 5,
                "description": "Highest value on the scale.",
                "type": "integer",
                "minimum": 2,
                "maximum": 11
              },
              "minLabel": {
                "description": "Caption shown under the lowest value.",
                "type": "string",
                "maxLength": 120
              },
              "maxLabel": {
                "description": "Caption shown under the highest value.",
                "type": "string",
                "maxLength": 120
              }
            },
            "required": [
              "id",
              "title",
              "required",
              "type",
              "min",
              "max"
            ],
            "additionalProperties": false,
            "description": "A numeric opinion / linear scale from min to max."
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 64,
                "pattern": "^[a-z0-9_-]+$",
                "description": "Stable question id. Unique within the form."
              },
              "title": {
                "type": "string",
                "minLength": 1,
                "maxLength": 2000,
                "description": "The question prompt shown to the respondent."
              },
              "description": {
                "description": "Optional helper text shown beneath the title.",
                "type": "string",
                "maxLength": 2000
              },
              "required": {
                "default": false,
                "description": "If true, the respondent must answer before submitting.",
                "type": "boolean"
              },
              "visibleIf": {
                "description": "Optional conditional logic. When set, the question is shown only if the rule passes; otherwise it is hidden and not validated.",
                "type": "object",
                "properties": {
                  "all": {
                    "description": "Every condition must pass (AND).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  },
                  "any": {
                    "description": "At least one condition must pass (OR).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  }
                },
                "additionalProperties": false
              },
              "scoring": {
                "description": "Optional quiz scoring for this question. Only graded when the form's settings.scoring.enabled is true.",
                "type": "object",
                "properties": {
                  "points": {
                    "type": "number",
                    "description": "Points awarded when the answer is correct."
                  },
                  "correct": {
                    "description": "Accepted correct answer(s). For choice questions use option ids; the answer is correct if it matches (multi-select must match the full set).",
                    "type": "array",
                    "items": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        }
                      ]
                    }
                  }
                },
                "required": [
                  "points"
                ],
                "additionalProperties": false
              },
              "media": {
                "description": "Optional image attached to this question (image-with-question pattern).",
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "maxLength": 2048,
                    "format": "uri",
                    "description": "Image URL. In the builder UI this is always a file from the workspace library."
                  },
                  "alt": {
                    "description": "Accessible alt text.",
                    "type": "string",
                    "maxLength": 200
                  },
                  "position": {
                    "description": "Layout relative to the question (top = default; left/right place image side-by-side with the question).",
                    "type": "string",
                    "enum": [
                      "top",
                      "bottom",
                      "left",
                      "right"
                    ]
                  },
                  "focalX": {
                    "description": "Horizontal focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "focalY": {
                    "description": "Vertical focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "brightness": {
                    "description": "Brightness offset as a percent (-100..100); 0 = unchanged. Useful for hero/cover images sitting under text.",
                    "type": "number",
                    "minimum": -100,
                    "maximum": 100
                  }
                },
                "required": [
                  "url"
                ],
                "additionalProperties": false
              },
              "theme": {
                "description": "Per-slide design override: tbn-* theme tokens that win over the form theme for this block's screen. Absent = inherit the form theme.",
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "type": "string"
                }
              },
              "type": {
                "type": "string",
                "const": "nps"
              },
              "lowLabel": {
                "default": "Not at all likely",
                "type": "string",
                "maxLength": 120
              },
              "highLabel": {
                "default": "Extremely likely",
                "type": "string",
                "maxLength": 120
              }
            },
            "required": [
              "id",
              "title",
              "required",
              "type",
              "lowLabel",
              "highLabel"
            ],
            "additionalProperties": false,
            "description": "Net Promoter Score: a fixed 0-10 likelihood scale (0-6 detractor, 7-8 passive, 9-10 promoter)."
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 64,
                "pattern": "^[a-z0-9_-]+$",
                "description": "Stable question id. Unique within the form."
              },
              "title": {
                "type": "string",
                "minLength": 1,
                "maxLength": 2000,
                "description": "The question prompt shown to the respondent."
              },
              "description": {
                "description": "Optional helper text shown beneath the title.",
                "type": "string",
                "maxLength": 2000
              },
              "required": {
                "default": false,
                "description": "If true, the respondent must answer before submitting.",
                "type": "boolean"
              },
              "visibleIf": {
                "description": "Optional conditional logic. When set, the question is shown only if the rule passes; otherwise it is hidden and not validated.",
                "type": "object",
                "properties": {
                  "all": {
                    "description": "Every condition must pass (AND).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  },
                  "any": {
                    "description": "At least one condition must pass (OR).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  }
                },
                "additionalProperties": false
              },
              "scoring": {
                "description": "Optional quiz scoring for this question. Only graded when the form's settings.scoring.enabled is true.",
                "type": "object",
                "properties": {
                  "points": {
                    "type": "number",
                    "description": "Points awarded when the answer is correct."
                  },
                  "correct": {
                    "description": "Accepted correct answer(s). For choice questions use option ids; the answer is correct if it matches (multi-select must match the full set).",
                    "type": "array",
                    "items": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        }
                      ]
                    }
                  }
                },
                "required": [
                  "points"
                ],
                "additionalProperties": false
              },
              "media": {
                "description": "Optional image attached to this question (image-with-question pattern).",
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "maxLength": 2048,
                    "format": "uri",
                    "description": "Image URL. In the builder UI this is always a file from the workspace library."
                  },
                  "alt": {
                    "description": "Accessible alt text.",
                    "type": "string",
                    "maxLength": 200
                  },
                  "position": {
                    "description": "Layout relative to the question (top = default; left/right place image side-by-side with the question).",
                    "type": "string",
                    "enum": [
                      "top",
                      "bottom",
                      "left",
                      "right"
                    ]
                  },
                  "focalX": {
                    "description": "Horizontal focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "focalY": {
                    "description": "Vertical focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "brightness": {
                    "description": "Brightness offset as a percent (-100..100); 0 = unchanged. Useful for hero/cover images sitting under text.",
                    "type": "number",
                    "minimum": -100,
                    "maximum": 100
                  }
                },
                "required": [
                  "url"
                ],
                "additionalProperties": false
              },
              "theme": {
                "description": "Per-slide design override: tbn-* theme tokens that win over the form theme for this block's screen. Absent = inherit the form theme.",
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "type": "string"
                }
              },
              "type": {
                "type": "string",
                "const": "csat"
              }
            },
            "required": [
              "id",
              "title",
              "required",
              "type"
            ],
            "additionalProperties": false,
            "description": "Customer-satisfaction score: a fixed 1-5 rating."
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 64,
                "pattern": "^[a-z0-9_-]+$",
                "description": "Stable question id. Unique within the form."
              },
              "title": {
                "type": "string",
                "minLength": 1,
                "maxLength": 2000,
                "description": "The question prompt shown to the respondent."
              },
              "description": {
                "description": "Optional helper text shown beneath the title.",
                "type": "string",
                "maxLength": 2000
              },
              "required": {
                "default": false,
                "description": "If true, the respondent must answer before submitting.",
                "type": "boolean"
              },
              "visibleIf": {
                "description": "Optional conditional logic. When set, the question is shown only if the rule passes; otherwise it is hidden and not validated.",
                "type": "object",
                "properties": {
                  "all": {
                    "description": "Every condition must pass (AND).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  },
                  "any": {
                    "description": "At least one condition must pass (OR).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  }
                },
                "additionalProperties": false
              },
              "scoring": {
                "description": "Optional quiz scoring for this question. Only graded when the form's settings.scoring.enabled is true.",
                "type": "object",
                "properties": {
                  "points": {
                    "type": "number",
                    "description": "Points awarded when the answer is correct."
                  },
                  "correct": {
                    "description": "Accepted correct answer(s). For choice questions use option ids; the answer is correct if it matches (multi-select must match the full set).",
                    "type": "array",
                    "items": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        }
                      ]
                    }
                  }
                },
                "required": [
                  "points"
                ],
                "additionalProperties": false
              },
              "media": {
                "description": "Optional image attached to this question (image-with-question pattern).",
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "maxLength": 2048,
                    "format": "uri",
                    "description": "Image URL. In the builder UI this is always a file from the workspace library."
                  },
                  "alt": {
                    "description": "Accessible alt text.",
                    "type": "string",
                    "maxLength": 200
                  },
                  "position": {
                    "description": "Layout relative to the question (top = default; left/right place image side-by-side with the question).",
                    "type": "string",
                    "enum": [
                      "top",
                      "bottom",
                      "left",
                      "right"
                    ]
                  },
                  "focalX": {
                    "description": "Horizontal focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "focalY": {
                    "description": "Vertical focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "brightness": {
                    "description": "Brightness offset as a percent (-100..100); 0 = unchanged. Useful for hero/cover images sitting under text.",
                    "type": "number",
                    "minimum": -100,
                    "maximum": 100
                  }
                },
                "required": [
                  "url"
                ],
                "additionalProperties": false
              },
              "theme": {
                "description": "Per-slide design override: tbn-* theme tokens that win over the form theme for this block's screen. Absent = inherit the form theme.",
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "type": "string"
                }
              },
              "type": {
                "type": "string",
                "const": "ranking"
              },
              "options": {
                "minItems": 2,
                "maxItems": 50,
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 64,
                      "pattern": "^[a-z0-9_-]+$",
                      "description": "Stable option id. Answers store this id, not the label, so labels can be edited freely later."
                    },
                    "label": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 500,
                      "description": "Human-readable text shown next to the choice."
                    }
                  },
                  "required": [
                    "id",
                    "label"
                  ],
                  "additionalProperties": false,
                  "description": "A selectable choice for select/dropdown questions."
                },
                "description": "Items the respondent drags into order of preference."
              }
            },
            "required": [
              "id",
              "title",
              "required",
              "type",
              "options"
            ],
            "additionalProperties": false,
            "description": "Respondent orders the options (1 = highest preference)."
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 64,
                "pattern": "^[a-z0-9_-]+$",
                "description": "Stable question id. Unique within the form."
              },
              "title": {
                "type": "string",
                "minLength": 1,
                "maxLength": 2000,
                "description": "The question prompt shown to the respondent."
              },
              "description": {
                "description": "Optional helper text shown beneath the title.",
                "type": "string",
                "maxLength": 2000
              },
              "required": {
                "default": false,
                "description": "If true, the respondent must answer before submitting.",
                "type": "boolean"
              },
              "visibleIf": {
                "description": "Optional conditional logic. When set, the question is shown only if the rule passes; otherwise it is hidden and not validated.",
                "type": "object",
                "properties": {
                  "all": {
                    "description": "Every condition must pass (AND).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  },
                  "any": {
                    "description": "At least one condition must pass (OR).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  }
                },
                "additionalProperties": false
              },
              "scoring": {
                "description": "Optional quiz scoring for this question. Only graded when the form's settings.scoring.enabled is true.",
                "type": "object",
                "properties": {
                  "points": {
                    "type": "number",
                    "description": "Points awarded when the answer is correct."
                  },
                  "correct": {
                    "description": "Accepted correct answer(s). For choice questions use option ids; the answer is correct if it matches (multi-select must match the full set).",
                    "type": "array",
                    "items": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        }
                      ]
                    }
                  }
                },
                "required": [
                  "points"
                ],
                "additionalProperties": false
              },
              "media": {
                "description": "Optional image attached to this question (image-with-question pattern).",
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "maxLength": 2048,
                    "format": "uri",
                    "description": "Image URL. In the builder UI this is always a file from the workspace library."
                  },
                  "alt": {
                    "description": "Accessible alt text.",
                    "type": "string",
                    "maxLength": 200
                  },
                  "position": {
                    "description": "Layout relative to the question (top = default; left/right place image side-by-side with the question).",
                    "type": "string",
                    "enum": [
                      "top",
                      "bottom",
                      "left",
                      "right"
                    ]
                  },
                  "focalX": {
                    "description": "Horizontal focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "focalY": {
                    "description": "Vertical focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "brightness": {
                    "description": "Brightness offset as a percent (-100..100); 0 = unchanged. Useful for hero/cover images sitting under text.",
                    "type": "number",
                    "minimum": -100,
                    "maximum": 100
                  }
                },
                "required": [
                  "url"
                ],
                "additionalProperties": false
              },
              "theme": {
                "description": "Per-slide design override: tbn-* theme tokens that win over the form theme for this block's screen. Absent = inherit the form theme.",
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "type": "string"
                }
              },
              "type": {
                "type": "string",
                "const": "matrix"
              },
              "rows": {
                "minItems": 1,
                "maxItems": 50,
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 64,
                      "pattern": "^[a-z0-9_-]+$",
                      "description": "Stable option id. Answers store this id, not the label, so labels can be edited freely later."
                    },
                    "label": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 500,
                      "description": "Human-readable text shown next to the choice."
                    }
                  },
                  "required": [
                    "id",
                    "label"
                  ],
                  "additionalProperties": false,
                  "description": "A selectable choice for select/dropdown questions."
                },
                "description": "Row statements being evaluated."
              },
              "columns": {
                "minItems": 2,
                "maxItems": 20,
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 64,
                      "pattern": "^[a-z0-9_-]+$",
                      "description": "Stable option id. Answers store this id, not the label, so labels can be edited freely later."
                    },
                    "label": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 500,
                      "description": "Human-readable text shown next to the choice."
                    }
                  },
                  "required": [
                    "id",
                    "label"
                  ],
                  "additionalProperties": false,
                  "description": "A selectable choice for select/dropdown questions."
                },
                "description": "Shared answer choices applied to every row."
              },
              "allowMultiplePerRow": {
                "default": false,
                "description": "If true, multiple columns may be chosen per row (checkbox grid); otherwise one (radio grid).",
                "type": "boolean"
              }
            },
            "required": [
              "id",
              "title",
              "required",
              "type",
              "rows",
              "columns",
              "allowMultiplePerRow"
            ],
            "additionalProperties": false,
            "description": "A grid where each row is rated against shared columns (Likert/matrix)."
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 64,
                "pattern": "^[a-z0-9_-]+$",
                "description": "Stable question id. Unique within the form."
              },
              "title": {
                "type": "string",
                "minLength": 1,
                "maxLength": 2000,
                "description": "The question prompt shown to the respondent."
              },
              "description": {
                "description": "Optional helper text shown beneath the title.",
                "type": "string",
                "maxLength": 2000
              },
              "required": {
                "default": false,
                "description": "If true, the respondent must answer before submitting.",
                "type": "boolean"
              },
              "visibleIf": {
                "description": "Optional conditional logic. When set, the question is shown only if the rule passes; otherwise it is hidden and not validated.",
                "type": "object",
                "properties": {
                  "all": {
                    "description": "Every condition must pass (AND).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  },
                  "any": {
                    "description": "At least one condition must pass (OR).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  }
                },
                "additionalProperties": false
              },
              "scoring": {
                "description": "Optional quiz scoring for this question. Only graded when the form's settings.scoring.enabled is true.",
                "type": "object",
                "properties": {
                  "points": {
                    "type": "number",
                    "description": "Points awarded when the answer is correct."
                  },
                  "correct": {
                    "description": "Accepted correct answer(s). For choice questions use option ids; the answer is correct if it matches (multi-select must match the full set).",
                    "type": "array",
                    "items": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        }
                      ]
                    }
                  }
                },
                "required": [
                  "points"
                ],
                "additionalProperties": false
              },
              "media": {
                "description": "Optional image attached to this question (image-with-question pattern).",
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "maxLength": 2048,
                    "format": "uri",
                    "description": "Image URL. In the builder UI this is always a file from the workspace library."
                  },
                  "alt": {
                    "description": "Accessible alt text.",
                    "type": "string",
                    "maxLength": 200
                  },
                  "position": {
                    "description": "Layout relative to the question (top = default; left/right place image side-by-side with the question).",
                    "type": "string",
                    "enum": [
                      "top",
                      "bottom",
                      "left",
                      "right"
                    ]
                  },
                  "focalX": {
                    "description": "Horizontal focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "focalY": {
                    "description": "Vertical focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "brightness": {
                    "description": "Brightness offset as a percent (-100..100); 0 = unchanged. Useful for hero/cover images sitting under text.",
                    "type": "number",
                    "minimum": -100,
                    "maximum": 100
                  }
                },
                "required": [
                  "url"
                ],
                "additionalProperties": false
              },
              "theme": {
                "description": "Per-slide design override: tbn-* theme tokens that win over the form theme for this block's screen. Absent = inherit the form theme.",
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "type": "string"
                }
              },
              "type": {
                "type": "string",
                "const": "picture_choice"
              },
              "options": {
                "default": [],
                "description": "Image-based choices. May be empty when `optionsConnector` is set - at fill time the grid is loaded from the connector (each option's image built from `connector.optionImage`).",
                "maxItems": 100,
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 64,
                      "pattern": "^[a-z0-9_-]+$",
                      "description": "Stable identifier. Must be unique within its scope (questions within a form; options within a question). Never reuse or repurpose an id once responses exist."
                    },
                    "label": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 500
                    },
                    "imageUrl": {
                      "type": "string",
                      "format": "uri",
                      "description": "Image displayed for this choice."
                    }
                  },
                  "required": [
                    "id",
                    "label",
                    "imageUrl"
                  ],
                  "additionalProperties": false
                }
              },
              "optionsConnector": {
                "type": "object",
                "properties": {
                  "connectorId": {
                    "type": "string",
                    "minLength": 1,
                    "description": "Must match a Connector id at the form's `connectors[]` array. Cross-checked in superRefine."
                  },
                  "refreshOn": {
                    "description": "When ANY of these question ids change value, re-invoke the connector. Used for cascading dropdowns (country → state → city).",
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1
                    }
                  }
                },
                "required": [
                  "connectorId"
                ],
                "additionalProperties": false,
                "description": "Wires a select-type question's options to a connector instead of (or in addition to) a static list."
              },
              "multiple": {
                "default": false,
                "description": "Allow selecting more than one image.",
                "type": "boolean"
              }
            },
            "required": [
              "id",
              "title",
              "required",
              "type",
              "options",
              "multiple"
            ],
            "additionalProperties": false,
            "description": "Pick one or more image-based options."
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 64,
                "pattern": "^[a-z0-9_-]+$",
                "description": "Stable question id. Unique within the form."
              },
              "title": {
                "type": "string",
                "minLength": 1,
                "maxLength": 2000,
                "description": "The question prompt shown to the respondent."
              },
              "description": {
                "description": "Optional helper text shown beneath the title.",
                "type": "string",
                "maxLength": 2000
              },
              "required": {
                "default": false,
                "description": "If true, the respondent must answer before submitting.",
                "type": "boolean"
              },
              "visibleIf": {
                "description": "Optional conditional logic. When set, the question is shown only if the rule passes; otherwise it is hidden and not validated.",
                "type": "object",
                "properties": {
                  "all": {
                    "description": "Every condition must pass (AND).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  },
                  "any": {
                    "description": "At least one condition must pass (OR).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  }
                },
                "additionalProperties": false
              },
              "scoring": {
                "description": "Optional quiz scoring for this question. Only graded when the form's settings.scoring.enabled is true.",
                "type": "object",
                "properties": {
                  "points": {
                    "type": "number",
                    "description": "Points awarded when the answer is correct."
                  },
                  "correct": {
                    "description": "Accepted correct answer(s). For choice questions use option ids; the answer is correct if it matches (multi-select must match the full set).",
                    "type": "array",
                    "items": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        }
                      ]
                    }
                  }
                },
                "required": [
                  "points"
                ],
                "additionalProperties": false
              },
              "media": {
                "description": "Optional image attached to this question (image-with-question pattern).",
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "maxLength": 2048,
                    "format": "uri",
                    "description": "Image URL. In the builder UI this is always a file from the workspace library."
                  },
                  "alt": {
                    "description": "Accessible alt text.",
                    "type": "string",
                    "maxLength": 200
                  },
                  "position": {
                    "description": "Layout relative to the question (top = default; left/right place image side-by-side with the question).",
                    "type": "string",
                    "enum": [
                      "top",
                      "bottom",
                      "left",
                      "right"
                    ]
                  },
                  "focalX": {
                    "description": "Horizontal focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "focalY": {
                    "description": "Vertical focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "brightness": {
                    "description": "Brightness offset as a percent (-100..100); 0 = unchanged. Useful for hero/cover images sitting under text.",
                    "type": "number",
                    "minimum": -100,
                    "maximum": 100
                  }
                },
                "required": [
                  "url"
                ],
                "additionalProperties": false
              },
              "theme": {
                "description": "Per-slide design override: tbn-* theme tokens that win over the form theme for this block's screen. Absent = inherit the form theme.",
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "type": "string"
                }
              },
              "type": {
                "type": "string",
                "const": "time"
              }
            },
            "required": [
              "id",
              "title",
              "required",
              "type"
            ],
            "additionalProperties": false,
            "description": "A time-of-day input (HH:MM)."
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 64,
                "pattern": "^[a-z0-9_-]+$",
                "description": "Stable question id. Unique within the form."
              },
              "type": {
                "type": "string",
                "const": "hidden"
              },
              "title": {
                "type": "string",
                "minLength": 1,
                "maxLength": 2000,
                "description": "Internal label for this hidden value (never shown to respondents)."
              }
            },
            "required": [
              "id",
              "type",
              "title"
            ],
            "additionalProperties": false,
            "description": "A hidden value populated from a URL query parameter whose key equals this question's id. Never rendered; for tracking (utm, ref) and prefill."
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 64,
                "pattern": "^[a-z0-9_-]+$",
                "description": "Stable question id. Unique within the form."
              },
              "type": {
                "type": "string",
                "const": "page_break"
              },
              "title": {
                "description": "Optional heading for the page that begins here.",
                "type": "string",
                "maxLength": 300
              },
              "description": {
                "description": "Optional helper text shown beneath the title.",
                "type": "string",
                "maxLength": 2000
              },
              "media": {
                "description": "Optional illustration shown with this section break.",
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "maxLength": 2048,
                    "format": "uri",
                    "description": "Image URL. In the builder UI this is always a file from the workspace library."
                  },
                  "alt": {
                    "description": "Accessible alt text.",
                    "type": "string",
                    "maxLength": 200
                  },
                  "position": {
                    "description": "Layout relative to the question (top = default; left/right place image side-by-side with the question).",
                    "type": "string",
                    "enum": [
                      "top",
                      "bottom",
                      "left",
                      "right"
                    ]
                  },
                  "focalX": {
                    "description": "Horizontal focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "focalY": {
                    "description": "Vertical focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "brightness": {
                    "description": "Brightness offset as a percent (-100..100); 0 = unchanged. Useful for hero/cover images sitting under text.",
                    "type": "number",
                    "minimum": -100,
                    "maximum": 100
                  }
                },
                "required": [
                  "url"
                ],
                "additionalProperties": false
              }
            },
            "required": [
              "id",
              "type"
            ],
            "additionalProperties": false,
            "description": "A page boundary. In one_per_page shape, questions are grouped into pages split at each page_break (Next/Back between pages). In single_page shape it renders as a section divider. Collects no answer."
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 64,
                "pattern": "^[a-z0-9_-]+$",
                "description": "Stable question id. Unique within the form."
              },
              "type": {
                "type": "string",
                "const": "statement"
              },
              "title": {
                "type": "string",
                "minLength": 1,
                "maxLength": 2000,
                "description": "Display-only heading or instruction text. Collects no answer."
              },
              "description": {
                "description": "Optional helper text shown beneath the title.",
                "type": "string",
                "maxLength": 2000
              },
              "visibleIf": {
                "description": "Optional conditional logic. When set, the question is shown only if the rule passes; otherwise it is hidden and not validated.",
                "type": "object",
                "properties": {
                  "all": {
                    "description": "Every condition must pass (AND).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  },
                  "any": {
                    "description": "At least one condition must pass (OR).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  }
                },
                "additionalProperties": false
              },
              "media": {
                "description": "Optional image - turns this block into an image step (with or without caption).",
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "maxLength": 2048,
                    "format": "uri",
                    "description": "Image URL. In the builder UI this is always a file from the workspace library."
                  },
                  "alt": {
                    "description": "Accessible alt text.",
                    "type": "string",
                    "maxLength": 200
                  },
                  "position": {
                    "description": "Layout relative to the question (top = default; left/right place image side-by-side with the question).",
                    "type": "string",
                    "enum": [
                      "top",
                      "bottom",
                      "left",
                      "right"
                    ]
                  },
                  "focalX": {
                    "description": "Horizontal focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "focalY": {
                    "description": "Vertical focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "brightness": {
                    "description": "Brightness offset as a percent (-100..100); 0 = unchanged. Useful for hero/cover images sitting under text.",
                    "type": "number",
                    "minimum": -100,
                    "maximum": 100
                  }
                },
                "required": [
                  "url"
                ],
                "additionalProperties": false
              }
            },
            "required": [
              "id",
              "type",
              "title"
            ],
            "additionalProperties": false,
            "description": "Display-only content (a section heading or instructions). Not an input - it never produces an answer."
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 64,
                "pattern": "^[a-z0-9_-]+$",
                "description": "Stable question id. Unique within the form."
              },
              "type": {
                "type": "string",
                "const": "calculated"
              },
              "title": {
                "type": "string",
                "minLength": 1,
                "maxLength": 2000,
                "description": "Display label for the calculated value (e.g. 'Subtotal', 'Estimated cost', 'Your age')."
              },
              "description": {
                "description": "Optional helper text shown beneath the title.",
                "type": "string",
                "maxLength": 2000
              },
              "visibleIf": {
                "description": "Optional conditional logic. When set, the question is shown only if the rule passes; otherwise it is hidden and not validated.",
                "type": "object",
                "properties": {
                  "all": {
                    "description": "Every condition must pass (AND).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  },
                  "any": {
                    "description": "At least one condition must pass (OR).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  }
                },
                "additionalProperties": false
              },
              "formula": {
                "type": "string",
                "minLength": 1,
                "maxLength": 2000,
                "description": "Formula in the calculator DSL. Reference other questions by id, e.g. `price * qty * (1 - discount)`. Supports + - * / %, comparisons, &&/||, and functions: min, max, sum, avg, abs, round, floor, ceil, sqrt, pow, if(cond, t, f). Missing or non-numeric answers are treated as 0."
              },
              "format": {
                "default": "number",
                "description": "How to render the computed value. `currency` uses currencyCode + locale; `percent` divides by 100 and appends %; `integer` rounds.",
                "type": "string",
                "enum": [
                  "number",
                  "currency",
                  "percent",
                  "integer"
                ]
              },
              "precision": {
                "default": 2,
                "description": "Number of decimal places shown (clamped to 0-6).",
                "type": "integer",
                "minimum": 0,
                "maximum": 6
              },
              "currencyCode": {
                "description": "ISO 4217 currency code for `format: 'currency'`. Defaults to USD if omitted.",
                "type": "string",
                "minLength": 3,
                "maxLength": 3
              },
              "prefix": {
                "description": "String shown before the value (e.g. '≈ ', 'Total: ').",
                "type": "string",
                "maxLength": 40
              },
              "suffix": {
                "description": "String shown after the value (e.g. ' months', ' / mo').",
                "type": "string",
                "maxLength": 40
              }
            },
            "required": [
              "id",
              "type",
              "title",
              "formula",
              "format",
              "precision"
            ],
            "additionalProperties": false,
            "description": "A read-only field whose value is computed live from other answers. Use for subtotals, totals with tax/discount, age-from-date, score aggregates, predicate flags (>= 18 → 1). Never asks the respondent for input."
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 64,
                "pattern": "^[a-z0-9_-]+$",
                "description": "Stable question id. Unique within the form."
              },
              "title": {
                "type": "string",
                "minLength": 1,
                "maxLength": 2000,
                "description": "The question prompt shown to the respondent."
              },
              "description": {
                "description": "Optional helper text shown beneath the title.",
                "type": "string",
                "maxLength": 2000
              },
              "required": {
                "default": false,
                "description": "If true, the respondent must answer before submitting.",
                "type": "boolean"
              },
              "visibleIf": {
                "description": "Optional conditional logic. When set, the question is shown only if the rule passes; otherwise it is hidden and not validated.",
                "type": "object",
                "properties": {
                  "all": {
                    "description": "Every condition must pass (AND).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  },
                  "any": {
                    "description": "At least one condition must pass (OR).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "questionId": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "The id of an EARLIER question whose answer is tested."
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "equals",
                            "not_equals",
                            "contains",
                            "not_contains",
                            "is_empty",
                            "is_not_empty",
                            "gt",
                            "lt"
                          ],
                          "description": "Comparison. 'equals/not_equals' match a value (for choice questions the value is the option id). 'contains/not_contains' test membership for multi-select. 'gt/lt' compare numbers. 'is_empty/is_not_empty' need no value."
                        },
                        "value": {
                          "description": "The value to compare against. For choice questions use the option id. Omit for is_empty/is_not_empty.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "questionId",
                        "operator"
                      ],
                      "additionalProperties": false,
                      "description": "A single condition on a prior answer."
                    }
                  }
                },
                "additionalProperties": false
              },
              "scoring": {
                "description": "Optional quiz scoring for this question. Only graded when the form's settings.scoring.enabled is true.",
                "type": "object",
                "properties": {
                  "points": {
                    "type": "number",
                    "description": "Points awarded when the answer is correct."
                  },
                  "correct": {
                    "description": "Accepted correct answer(s). For choice questions use option ids; the answer is correct if it matches (multi-select must match the full set).",
                    "type": "array",
                    "items": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        }
                      ]
                    }
                  }
                },
                "required": [
                  "points"
                ],
                "additionalProperties": false
              },
              "media": {
                "description": "Optional image attached to this question (image-with-question pattern).",
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "maxLength": 2048,
                    "format": "uri",
                    "description": "Image URL. In the builder UI this is always a file from the workspace library."
                  },
                  "alt": {
                    "description": "Accessible alt text.",
                    "type": "string",
                    "maxLength": 200
                  },
                  "position": {
                    "description": "Layout relative to the question (top = default; left/right place image side-by-side with the question).",
                    "type": "string",
                    "enum": [
                      "top",
                      "bottom",
                      "left",
                      "right"
                    ]
                  },
                  "focalX": {
                    "description": "Horizontal focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "focalY": {
                    "description": "Vertical focal point as a 0..1 fraction (default 0.5).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "brightness": {
                    "description": "Brightness offset as a percent (-100..100); 0 = unchanged. Useful for hero/cover images sitting under text.",
                    "type": "number",
                    "minimum": -100,
                    "maximum": 100
                  }
                },
                "required": [
                  "url"
                ],
                "additionalProperties": false
              },
              "theme": {
                "description": "Per-slide design override: tbn-* theme tokens that win over the form theme for this block's screen. Absent = inherit the form theme.",
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "type": "string"
                }
              },
              "type": {
                "type": "string",
                "const": "appointment"
              },
              "durationMin": {
                "default": 30,
                "type": "integer",
                "minimum": 5,
                "maximum": 480
              },
              "timezone": {
                "default": "Asia/Kolkata",
                "type": "string",
                "maxLength": 60
              },
              "daysAhead": {
                "default": 14,
                "type": "integer",
                "minimum": 1,
                "maximum": 90
              },
              "weekdays": {
                "default": [
                  1,
                  2,
                  3,
                  4,
                  5
                ],
                "description": "ISO weekday numbers (1=Mon..7=Sun) when slots are offered.",
                "type": "array",
                "items": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 7
                }
              },
              "startHour": {
                "default": 9,
                "type": "integer",
                "minimum": 0,
                "maximum": 23
              },
              "endHour": {
                "default": 17,
                "type": "integer",
                "minimum": 1,
                "maximum": 24
              },
              "minNoticeMin": {
                "default": 60,
                "type": "integer",
                "minimum": 0,
                "maximum": 10080
              },
              "bufferMin": {
                "default": 0,
                "type": "integer",
                "minimum": 0,
                "maximum": 120
              },
              "eventTitle": {
                "default": "Meeting with {{name}}",
                "description": "Calendar event title. Use {{question_id}} or {{full_name}} for answer piping.",
                "type": "string",
                "maxLength": 200
              },
              "eventDescription": {
                "default": "",
                "type": "string",
                "maxLength": 2000
              },
              "addMeet": {
                "default": true,
                "type": "boolean"
              },
              "attendeeEmailFieldId": {
                "type": "string"
              },
              "attendeeNameFieldId": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "title",
              "required",
              "type",
              "durationMin",
              "timezone",
              "daysAhead",
              "weekdays",
              "startHour",
              "endHour",
              "minNoticeMin",
              "bufferMin",
              "eventTitle",
              "eventDescription",
              "addMeet"
            ],
            "additionalProperties": false,
            "description": "An appointment-booking question. Respondent picks a slot from the owner's available window; on submit, Askery creates a Google Calendar event with optional Meet link and invites the respondent. Requires Google connection on the workspace + Calendar scope."
          }
        ],
        "description": "A single form question. The `type` field is the discriminator and fully determines which other fields apply."
      },
      "description": "Ordered list of questions. Display order IS array order. Every question id must be unique within this array."
    },
    "settings": {
      "default": {
        "submitButtonLabel": "Submit",
        "confirmationMessage": "Thanks - your response has been recorded.",
        "responsePage": {
          "title": "",
          "subtitle": "",
          "body": "",
          "showFormLogo": true,
          "showFormTitle": false,
          "background": "match",
          "backgroundColor": "",
          "heroImage": {
            "url": "",
            "alt": ""
          },
          "backgroundImage": {
            "url": "",
            "overlay": 40,
            "blur": 0
          },
          "shape": "hero",
          "spacing": "comfy",
          "accentColor": "",
          "cardStyle": "theme",
          "cardColor": "",
          "confetti": true,
          "cta": {
            "enabled": false,
            "label": "",
            "url": "",
            "style": "solid"
          },
          "secondaryCta": {
            "enabled": false,
            "label": "",
            "url": "",
            "style": "outline"
          },
          "share": {
            "enabled": false,
            "providers": []
          },
          "redirect": {
            "enabled": false,
            "seconds": 10,
            "url": ""
          },
          "branding": "hide",
          "customCss": "",
          "template": "custom",
          "blocks": [],
          "theme": {},
          "showScore": "auto"
        },
        "collectEmail": false,
        "allowMultipleSubmissions": true,
        "shape": "one_per_page",
        "width": "full",
        "size": "large",
        "density": "comfortable"
      },
      "type": "object",
      "properties": {
        "submitButtonLabel": {
          "default": "Submit",
          "description": "Label on the submit button.",
          "type": "string",
          "minLength": 1,
          "maxLength": 80
        },
        "confirmationMessage": {
          "default": "Thanks - your response has been recorded.",
          "description": "Legacy: plain-text message shown after submit when responsePage.body is empty. New forms should configure `responsePage` instead - this field is kept for backward compatibility with forms saved before responsePage existed.",
          "type": "string",
          "maxLength": 2000
        },
        "responsePage": {
          "default": {
            "title": "",
            "subtitle": "",
            "body": "",
            "showFormLogo": true,
            "showFormTitle": false,
            "background": "match",
            "backgroundColor": "",
            "heroImage": {
              "url": "",
              "alt": ""
            },
            "backgroundImage": {
              "url": "",
              "overlay": 40,
              "blur": 0
            },
            "shape": "hero",
            "spacing": "comfy",
            "accentColor": "",
            "cardStyle": "theme",
            "cardColor": "",
            "confetti": true,
            "cta": {
              "enabled": false,
              "label": "",
              "url": "",
              "style": "solid"
            },
            "secondaryCta": {
              "enabled": false,
              "label": "",
              "url": "",
              "style": "outline"
            },
            "share": {
              "enabled": false,
              "providers": []
            },
            "redirect": {
              "enabled": false,
              "seconds": 10,
              "url": ""
            },
            "branding": "hide",
            "customCss": "",
            "template": "custom",
            "blocks": [],
            "theme": {},
            "showScore": "auto"
          },
          "description": "Configuration for the response page (what respondents see after submitting). Same shape applies whether Form Intelligence is on or off - when FI is on, only the body is replaced by the AI output; when FI is off, the body comes from `responsePage.body`.",
          "type": "object",
          "properties": {
            "title": {
              "default": "",
              "description": "Heading shown at the top of the response page. Empty = use a sensible default ('Thanks!' or 'Your result').",
              "type": "string",
              "maxLength": 120
            },
            "subtitle": {
              "default": "",
              "description": "Optional small line under the title.",
              "type": "string",
              "maxLength": 240
            },
            "body": {
              "default": "",
              "description": "The thank-you message body. Used only when Form Intelligence is off; when FI is on the body comes from the engine output. Falls back to `confirmationMessage` if both are empty.",
              "type": "string",
              "maxLength": 4000
            },
            "showFormLogo": {
              "default": true,
              "description": "Show the form's logo at the top of the response page.",
              "type": "boolean"
            },
            "showFormTitle": {
              "default": false,
              "description": "Show the original form's title above the response. Off by default - the response usually has its own title.",
              "type": "boolean"
            },
            "background": {
              "default": "match",
              "description": "'match' inherits the form's theme; 'custom' uses backgroundColor; 'plain' renders on white with minimal chrome.",
              "type": "string",
              "enum": [
                "match",
                "custom",
                "plain"
              ]
            },
            "backgroundColor": {
              "default": "",
              "description": "Hex / CSS colour used when background='custom'. Empty = use the form's accent-soft.",
              "type": "string",
              "maxLength": 20
            },
            "confetti": {
              "default": true,
              "description": "Show a brief confetti animation when the response page loads.",
              "type": "boolean"
            },
            "heroImage": {
              "default": {
                "url": "",
                "alt": ""
              },
              "description": "Optional image shown at the top of the response page (above the title). Empty url = no hero image.",
              "type": "object",
              "properties": {
                "url": {
                  "default": "",
                  "type": "string",
                  "maxLength": 2000
                },
                "alt": {
                  "default": "",
                  "type": "string",
                  "maxLength": 200
                }
              },
              "required": [
                "url",
                "alt"
              ],
              "additionalProperties": false
            },
            "backgroundImage": {
              "default": {
                "url": "",
                "overlay": 40,
                "blur": 0
              },
              "description": "Optional full-bleed background image. `overlay` is a dark overlay percentage to keep text readable; `blur` softens the image.",
              "type": "object",
              "properties": {
                "url": {
                  "default": "",
                  "type": "string",
                  "maxLength": 2000
                },
                "overlay": {
                  "default": 40,
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 100
                },
                "blur": {
                  "default": 0,
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 20
                }
              },
              "required": [
                "url",
                "overlay",
                "blur"
              ],
              "additionalProperties": false
            },
            "shape": {
              "default": "hero",
              "description": "FI result-page layout. 'hero' = big score top-center (current default). 'cards' = section grid, no big hero. 'article' = long-form readable narrative. 'minimal' = text-only, no chrome. Ignored when FI is off.",
              "type": "string",
              "enum": [
                "hero",
                "cards",
                "article",
                "minimal"
              ]
            },
            "spacing": {
              "default": "comfy",
              "description": "Padding density across the response page.",
              "type": "string",
              "enum": [
                "compact",
                "comfy",
                "spacious"
              ]
            },
            "accentColor": {
              "default": "",
              "description": "Overrides the form theme's accent colour for the CTA + FI score. Empty = inherit form theme.",
              "type": "string",
              "maxLength": 20
            },
            "cardStyle": {
              "default": "theme",
              "description": "How FI result cards relate to the background. 'theme' uses the form's own card colour (--tbn-card-bg). 'glass' overlays a translucent white over whatever background you picked. 'solid' uses the explicit `cardColor`. 'outline' has no fill - border only. Lets owners decide whether cards inherit the background or stand apart from it.",
              "type": "string",
              "enum": [
                "theme",
                "glass",
                "solid",
                "outline"
              ]
            },
            "cardColor": {
              "default": "",
              "description": "Hex / CSS colour for cards when cardStyle='solid'. Ignored otherwise.",
              "type": "string",
              "maxLength": 20
            },
            "cta": {
              "default": {
                "enabled": false,
                "label": "",
                "url": "",
                "style": "solid"
              },
              "description": "Primary call-to-action button shown below the response (booking link, next-step link, etc).",
              "type": "object",
              "properties": {
                "enabled": {
                  "default": false,
                  "type": "boolean"
                },
                "label": {
                  "default": "",
                  "type": "string",
                  "maxLength": 40
                },
                "url": {
                  "default": "",
                  "type": "string",
                  "maxLength": 500
                },
                "style": {
                  "default": "solid",
                  "type": "string",
                  "enum": [
                    "solid",
                    "outline",
                    "ghost"
                  ]
                }
              },
              "required": [
                "enabled",
                "label",
                "url",
                "style"
              ],
              "additionalProperties": false
            },
            "secondaryCta": {
              "default": {
                "enabled": false,
                "label": "",
                "url": "",
                "style": "outline"
              },
              "description": "Optional secondary CTA (sits next to the primary). Common pattern: primary = book; secondary = learn more / download / share).",
              "type": "object",
              "properties": {
                "enabled": {
                  "default": false,
                  "type": "boolean"
                },
                "label": {
                  "default": "",
                  "type": "string",
                  "maxLength": 40
                },
                "url": {
                  "default": "",
                  "type": "string",
                  "maxLength": 500
                },
                "style": {
                  "default": "outline",
                  "type": "string",
                  "enum": [
                    "solid",
                    "outline",
                    "ghost"
                  ]
                }
              },
              "required": [
                "enabled",
                "label",
                "url",
                "style"
              ],
              "additionalProperties": false
            },
            "share": {
              "default": {
                "enabled": false,
                "providers": []
              },
              "description": "Share buttons shown below the body. Useful for personality quizzes, scorecards, etc.",
              "type": "object",
              "properties": {
                "enabled": {
                  "default": false,
                  "type": "boolean"
                },
                "providers": {
                  "default": [],
                  "type": "array",
                  "items": {
                    "type": "string",
                    "enum": [
                      "twitter",
                      "linkedin",
                      "whatsapp",
                      "copy"
                    ]
                  }
                }
              },
              "required": [
                "enabled",
                "providers"
              ],
              "additionalProperties": false
            },
            "redirect": {
              "default": {
                "enabled": false,
                "seconds": 10,
                "url": ""
              },
              "description": "Automatically navigate to `url` N seconds after the response page loads.",
              "type": "object",
              "properties": {
                "enabled": {
                  "default": false,
                  "type": "boolean"
                },
                "seconds": {
                  "default": 10,
                  "type": "integer",
                  "minimum": 2,
                  "maximum": 120
                },
                "url": {
                  "default": "",
                  "type": "string",
                  "maxLength": 500
                }
              },
              "required": [
                "enabled",
                "seconds",
                "url"
              ],
              "additionalProperties": false
            },
            "branding": {
              "default": "hide",
              "description": "'show' renders the 'Powered by Askery' mark; 'hide' removes it. Off by default; paid plans opt in by setting 'show'. Free plans always render the mark regardless of this value.",
              "type": "string",
              "enum": [
                "show",
                "hide"
              ]
            },
            "customCss": {
              "default": "",
              "description": "Escape hatch for power users. CSS injected into the response page only; scoped via `.tbn-form--result` / `.tbn-confirm`. Use sparingly.",
              "type": "string",
              "maxLength": 8000
            },
            "template": {
              "default": "custom",
              "description": "Template preset the page was seeded from. 'custom' = freely composed; 'blank' = empty page the owner is building from scratch. Switching the template via set_response_template REPLACES the current blocks with the seed for the new template.",
              "type": "string",
              "enum": [
                "profile",
                "score",
                "magazine",
                "actionplan",
                "dashboard",
                "receipt",
                "letter",
                "certificate",
                "polaroid",
                "timeline",
                "terminal",
                "brutalist",
                "cinema",
                "boutique",
                "blank",
                "custom"
              ]
            },
            "blocks": {
              "default": [],
              "description": "Ordered list of typed blocks composing the page. Empty = use legacy `shape`-based renderer.",
              "maxItems": 60,
              "type": "array",
              "items": {
                "oneOf": [
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "const": "archetype-hero"
                      },
                      "id": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 64,
                        "pattern": "^[a-z][a-z0-9-]*$",
                        "description": "Stable id (kebab-case). EditOps target blocks by id - never rename an id once a block is referenced by saved data."
                      },
                      "visible": {
                        "default": true,
                        "description": "Show or hide the block on the rendered page.",
                        "type": "boolean"
                      },
                      "style": {
                        "type": "object",
                        "properties": {
                          "bg": {
                            "description": "Block background colour. Hex (#rrggbb), rgb(), or CSS keyword.",
                            "type": "string",
                            "maxLength": 40
                          },
                          "fg": {
                            "description": "Body text colour for this block.",
                            "type": "string",
                            "maxLength": 40
                          },
                          "accent": {
                            "description": "Accent colour for highlights inside the block (numbers, pills, borders).",
                            "type": "string",
                            "maxLength": 40
                          },
                          "bodyFont": {
                            "description": "CSS font-family stack OR Google Fonts family name for body text.",
                            "type": "string",
                            "maxLength": 120
                          },
                          "headingFont": {
                            "description": "CSS font-family stack OR Google Fonts family name for headings.",
                            "type": "string",
                            "maxLength": 120
                          },
                          "paddingY": {
                            "type": "string",
                            "enum": [
                              "none",
                              "compact",
                              "comfy",
                              "spacious"
                            ],
                            "description": "Vertical padding around the block. 'none' = flush against neighbours; 'spacious' = generous breathing room."
                          },
                          "align": {
                            "type": "string",
                            "enum": [
                              "left",
                              "center",
                              "right"
                            ],
                            "description": "Text alignment inside the block."
                          },
                          "maxWidth": {
                            "type": "string",
                            "enum": [
                              "narrow",
                              "default",
                              "wide",
                              "full"
                            ],
                            "description": "Constrain block width. 'narrow' = readable column (~640px), 'wide' = full content area, 'full' = bleed to edges."
                          }
                        },
                        "additionalProperties": false,
                        "description": "Per-block style override. Page-level theme fills in any omitted key."
                      },
                      "content": {
                        "type": "object",
                        "properties": {
                          "title": {
                            "type": "object",
                            "properties": {
                              "source": {
                                "default": "outcome",
                                "description": "Where the value comes from. 'outcome' = bind to a field on the AI outcome (see outcomePath). 'static' = use the literal `text` below.",
                                "type": "string",
                                "enum": [
                                  "outcome",
                                  "static"
                                ]
                              },
                              "outcomePath": {
                                "description": "Dot-path into the outcome (e.g. 'archetype', 'score', 'traits[0]'). Required when source='outcome'. Renderer falls back to `text` if path is missing.",
                                "type": "string",
                                "maxLength": 80
                              },
                              "text": {
                                "description": "Literal text. Used when source='static' or as a fallback if the bound outcome field is absent.",
                                "type": "string",
                                "maxLength": 400
                              }
                            },
                            "required": [
                              "source"
                            ],
                            "additionalProperties": false,
                            "description": "The big archetype name. Defaults to outcomePath='archetype'."
                          },
                          "tagline": {
                            "description": "One-line subtitle under the title. Often bound to outcomePath='tagline'.",
                            "type": "object",
                            "properties": {
                              "source": {
                                "default": "outcome",
                                "description": "Where the value comes from. 'outcome' = bind to a field on the AI outcome (see outcomePath). 'static' = use the literal `text` below.",
                                "type": "string",
                                "enum": [
                                  "outcome",
                                  "static"
                                ]
                              },
                              "outcomePath": {
                                "description": "Dot-path into the outcome (e.g. 'archetype', 'score', 'traits[0]'). Required when source='outcome'. Renderer falls back to `text` if path is missing.",
                                "type": "string",
                                "maxLength": 80
                              },
                              "text": {
                                "description": "Literal text. Used when source='static' or as a fallback if the bound outcome field is absent.",
                                "type": "string",
                                "maxLength": 400
                              }
                            },
                            "required": [
                              "source"
                            ],
                            "additionalProperties": false
                          },
                          "eyebrow": {
                            "description": "Tiny uppercase label above the title (e.g. 'YOUR EATER TYPE').",
                            "type": "string",
                            "maxLength": 80
                          }
                        },
                        "required": [
                          "title"
                        ],
                        "additionalProperties": false,
                        "description": "Big identity hero. Title can carry serif/italic styling."
                      }
                    },
                    "required": [
                      "type",
                      "id",
                      "visible",
                      "content"
                    ],
                    "additionalProperties": false,
                    "description": "Identity-led hero. Best when the form is descriptive/preference (e.g. eater type, persona). Hides any score implicitly."
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "const": "score-dial"
                      },
                      "id": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 64,
                        "pattern": "^[a-z][a-z0-9-]*$",
                        "description": "Stable id (kebab-case). EditOps target blocks by id - never rename an id once a block is referenced by saved data."
                      },
                      "visible": {
                        "default": true,
                        "description": "Show or hide the block on the rendered page.",
                        "type": "boolean"
                      },
                      "style": {
                        "type": "object",
                        "properties": {
                          "bg": {
                            "description": "Block background colour. Hex (#rrggbb), rgb(), or CSS keyword.",
                            "type": "string",
                            "maxLength": 40
                          },
                          "fg": {
                            "description": "Body text colour for this block.",
                            "type": "string",
                            "maxLength": 40
                          },
                          "accent": {
                            "description": "Accent colour for highlights inside the block (numbers, pills, borders).",
                            "type": "string",
                            "maxLength": 40
                          },
                          "bodyFont": {
                            "description": "CSS font-family stack OR Google Fonts family name for body text.",
                            "type": "string",
                            "maxLength": 120
                          },
                          "headingFont": {
                            "description": "CSS font-family stack OR Google Fonts family name for headings.",
                            "type": "string",
                            "maxLength": 120
                          },
                          "paddingY": {
                            "type": "string",
                            "enum": [
                              "none",
                              "compact",
                              "comfy",
                              "spacious"
                            ],
                            "description": "Vertical padding around the block. 'none' = flush against neighbours; 'spacious' = generous breathing room."
                          },
                          "align": {
                            "type": "string",
                            "enum": [
                              "left",
                              "center",
                              "right"
                            ],
                            "description": "Text alignment inside the block."
                          },
                          "maxWidth": {
                            "type": "string",
                            "enum": [
                              "narrow",
                              "default",
                              "wide",
                              "full"
                            ],
                            "description": "Constrain block width. 'narrow' = readable column (~640px), 'wide' = full content area, 'full' = bleed to edges."
                          }
                        },
                        "additionalProperties": false,
                        "description": "Per-block style override. Page-level theme fills in any omitted key."
                      },
                      "content": {
                        "type": "object",
                        "properties": {
                          "source": {
                            "default": "outcome",
                            "description": "'outcome' pulls outcome.score; 'static' uses `value`.",
                            "type": "string",
                            "enum": [
                              "outcome",
                              "static"
                            ]
                          },
                          "value": {
                            "description": "Static score value.",
                            "type": "number",
                            "minimum": 0,
                            "maximum": 100
                          },
                          "max": {
                            "default": 100,
                            "description": "Denominator.",
                            "type": "integer",
                            "minimum": 2,
                            "maximum": 1000
                          },
                          "label": {
                            "default": "Overall match",
                            "description": "Small label under the dial.",
                            "type": "string",
                            "maxLength": 60
                          },
                          "showNumber": {
                            "default": true,
                            "description": "Render the numeric value inside the dial. Off = ring only.",
                            "type": "boolean"
                          }
                        },
                        "required": [
                          "source",
                          "max",
                          "label",
                          "showNumber"
                        ],
                        "additionalProperties": false,
                        "description": "Animated circular dial bound to the AI outcome score."
                      }
                    },
                    "required": [
                      "type",
                      "id",
                      "visible",
                      "content"
                    ],
                    "additionalProperties": false,
                    "description": "Score-led hero. Use when the form is evaluative (quiz, fit assessment). Auto-detection in codegen avoids generating one for descriptive forms."
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "const": "dimension-bars"
                      },
                      "id": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 64,
                        "pattern": "^[a-z][a-z0-9-]*$",
                        "description": "Stable id (kebab-case). EditOps target blocks by id - never rename an id once a block is referenced by saved data."
                      },
                      "visible": {
                        "default": true,
                        "description": "Show or hide the block on the rendered page.",
                        "type": "boolean"
                      },
                      "style": {
                        "type": "object",
                        "properties": {
                          "bg": {
                            "description": "Block background colour. Hex (#rrggbb), rgb(), or CSS keyword.",
                            "type": "string",
                            "maxLength": 40
                          },
                          "fg": {
                            "description": "Body text colour for this block.",
                            "type": "string",
                            "maxLength": 40
                          },
                          "accent": {
                            "description": "Accent colour for highlights inside the block (numbers, pills, borders).",
                            "type": "string",
                            "maxLength": 40
                          },
                          "bodyFont": {
                            "description": "CSS font-family stack OR Google Fonts family name for body text.",
                            "type": "string",
                            "maxLength": 120
                          },
                          "headingFont": {
                            "description": "CSS font-family stack OR Google Fonts family name for headings.",
                            "type": "string",
                            "maxLength": 120
                          },
                          "paddingY": {
                            "type": "string",
                            "enum": [
                              "none",
                              "compact",
                              "comfy",
                              "spacious"
                            ],
                            "description": "Vertical padding around the block. 'none' = flush against neighbours; 'spacious' = generous breathing room."
                          },
                          "align": {
                            "type": "string",
                            "enum": [
                              "left",
                              "center",
                              "right"
                            ],
                            "description": "Text alignment inside the block."
                          },
                          "maxWidth": {
                            "type": "string",
                            "enum": [
                              "narrow",
                              "default",
                              "wide",
                              "full"
                            ],
                            "description": "Constrain block width. 'narrow' = readable column (~640px), 'wide' = full content area, 'full' = bleed to edges."
                          }
                        },
                        "additionalProperties": false,
                        "description": "Per-block style override. Page-level theme fills in any omitted key."
                      },
                      "content": {
                        "type": "object",
                        "properties": {
                          "source": {
                            "default": "outcome",
                            "description": "'outcome' pulls outcome.dimensions ({label, value}[]); 'static' uses `dimensions`.",
                            "type": "string",
                            "enum": [
                              "outcome",
                              "static"
                            ]
                          },
                          "dimensions": {
                            "description": "Static dimension list. Used when source='static'.",
                            "maxItems": 12,
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "label": {
                                  "type": "string",
                                  "maxLength": 80
                                },
                                "value": {
                                  "type": "number",
                                  "minimum": 0,
                                  "maximum": 100
                                }
                              },
                              "required": [
                                "label",
                                "value"
                              ],
                              "additionalProperties": false
                            }
                          },
                          "maxValue": {
                            "default": 100,
                            "type": "integer",
                            "minimum": 2,
                            "maximum": 1000
                          },
                          "showValues": {
                            "default": true,
                            "description": "Print the numeric value at the end of each bar.",
                            "type": "boolean"
                          }
                        },
                        "required": [
                          "source",
                          "maxValue",
                          "showValues"
                        ],
                        "additionalProperties": false,
                        "description": "Horizontal stacked bars, one per scoring dimension."
                      }
                    },
                    "required": [
                      "type",
                      "id",
                      "visible",
                      "content"
                    ],
                    "additionalProperties": false,
                    "description": "Multi-axis score visualisation. Pairs naturally with score-dial."
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "const": "trait-chips"
                      },
                      "id": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 64,
                        "pattern": "^[a-z][a-z0-9-]*$",
                        "description": "Stable id (kebab-case). EditOps target blocks by id - never rename an id once a block is referenced by saved data."
                      },
                      "visible": {
                        "default": true,
                        "description": "Show or hide the block on the rendered page.",
                        "type": "boolean"
                      },
                      "style": {
                        "type": "object",
                        "properties": {
                          "bg": {
                            "description": "Block background colour. Hex (#rrggbb), rgb(), or CSS keyword.",
                            "type": "string",
                            "maxLength": 40
                          },
                          "fg": {
                            "description": "Body text colour for this block.",
                            "type": "string",
                            "maxLength": 40
                          },
                          "accent": {
                            "description": "Accent colour for highlights inside the block (numbers, pills, borders).",
                            "type": "string",
                            "maxLength": 40
                          },
                          "bodyFont": {
                            "description": "CSS font-family stack OR Google Fonts family name for body text.",
                            "type": "string",
                            "maxLength": 120
                          },
                          "headingFont": {
                            "description": "CSS font-family stack OR Google Fonts family name for headings.",
                            "type": "string",
                            "maxLength": 120
                          },
                          "paddingY": {
                            "type": "string",
                            "enum": [
                              "none",
                              "compact",
                              "comfy",
                              "spacious"
                            ],
                            "description": "Vertical padding around the block. 'none' = flush against neighbours; 'spacious' = generous breathing room."
                          },
                          "align": {
                            "type": "string",
                            "enum": [
                              "left",
                              "center",
                              "right"
                            ],
                            "description": "Text alignment inside the block."
                          },
                          "maxWidth": {
                            "type": "string",
                            "enum": [
                              "narrow",
                              "default",
                              "wide",
                              "full"
                            ],
                            "description": "Constrain block width. 'narrow' = readable column (~640px), 'wide' = full content area, 'full' = bleed to edges."
                          }
                        },
                        "additionalProperties": false,
                        "description": "Per-block style override. Page-level theme fills in any omitted key."
                      },
                      "content": {
                        "type": "object",
                        "properties": {
                          "source": {
                            "default": "outcome",
                            "description": "'outcome' pulls outcome.traits[].",
                            "type": "string",
                            "enum": [
                              "outcome",
                              "static"
                            ]
                          },
                          "traits": {
                            "description": "Static traits. Used when source='static'.",
                            "maxItems": 20,
                            "type": "array",
                            "items": {
                              "type": "string",
                              "maxLength": 60
                            }
                          },
                          "label": {
                            "default": "YOUR TRAITS",
                            "description": "Eyebrow label above the chip row. Empty hides it.",
                            "type": "string",
                            "maxLength": 80
                          },
                          "layout": {
                            "default": "outline",
                            "description": "Pill visual style.",
                            "type": "string",
                            "enum": [
                              "outline",
                              "filled",
                              "subtle"
                            ]
                          }
                        },
                        "required": [
                          "source",
                          "label",
                          "layout"
                        ],
                        "additionalProperties": false,
                        "description": "A wrap row of trait pills."
                      }
                    },
                    "required": [
                      "type",
                      "id",
                      "visible",
                      "content"
                    ],
                    "additionalProperties": false,
                    "description": "Trait/tag cloud. Useful for personality profiles, eater types, etc."
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "const": "prose"
                      },
                      "id": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 64,
                        "pattern": "^[a-z][a-z0-9-]*$",
                        "description": "Stable id (kebab-case). EditOps target blocks by id - never rename an id once a block is referenced by saved data."
                      },
                      "visible": {
                        "default": true,
                        "description": "Show or hide the block on the rendered page.",
                        "type": "boolean"
                      },
                      "style": {
                        "type": "object",
                        "properties": {
                          "bg": {
                            "description": "Block background colour. Hex (#rrggbb), rgb(), or CSS keyword.",
                            "type": "string",
                            "maxLength": 40
                          },
                          "fg": {
                            "description": "Body text colour for this block.",
                            "type": "string",
                            "maxLength": 40
                          },
                          "accent": {
                            "description": "Accent colour for highlights inside the block (numbers, pills, borders).",
                            "type": "string",
                            "maxLength": 40
                          },
                          "bodyFont": {
                            "description": "CSS font-family stack OR Google Fonts family name for body text.",
                            "type": "string",
                            "maxLength": 120
                          },
                          "headingFont": {
                            "description": "CSS font-family stack OR Google Fonts family name for headings.",
                            "type": "string",
                            "maxLength": 120
                          },
                          "paddingY": {
                            "type": "string",
                            "enum": [
                              "none",
                              "compact",
                              "comfy",
                              "spacious"
                            ],
                            "description": "Vertical padding around the block. 'none' = flush against neighbours; 'spacious' = generous breathing room."
                          },
                          "align": {
                            "type": "string",
                            "enum": [
                              "left",
                              "center",
                              "right"
                            ],
                            "description": "Text alignment inside the block."
                          },
                          "maxWidth": {
                            "type": "string",
                            "enum": [
                              "narrow",
                              "default",
                              "wide",
                              "full"
                            ],
                            "description": "Constrain block width. 'narrow' = readable column (~640px), 'wide' = full content area, 'full' = bleed to edges."
                          }
                        },
                        "additionalProperties": false,
                        "description": "Per-block style override. Page-level theme fills in any omitted key."
                      },
                      "content": {
                        "type": "object",
                        "properties": {
                          "heading": {
                            "description": "Optional heading above the paragraph.",
                            "type": "string",
                            "maxLength": 120
                          },
                          "body": {
                            "type": "object",
                            "properties": {
                              "source": {
                                "default": "outcome",
                                "description": "Where the value comes from. 'outcome' = bind to a field on the AI outcome (see outcomePath). 'static' = use the literal `text` below.",
                                "type": "string",
                                "enum": [
                                  "outcome",
                                  "static"
                                ]
                              },
                              "outcomePath": {
                                "description": "Dot-path into the outcome (e.g. 'archetype', 'score', 'traits[0]'). Required when source='outcome'. Renderer falls back to `text` if path is missing.",
                                "type": "string",
                                "maxLength": 80
                              },
                              "text": {
                                "description": "Literal text. Used when source='static' or as a fallback if the bound outcome field is absent.",
                                "type": "string",
                                "maxLength": 400
                              }
                            },
                            "required": [
                              "source"
                            ],
                            "additionalProperties": false,
                            "description": "Paragraph body. Either bound to an outcome field (e.g. 'tagline') or static text. Supports `\\n` for line breaks."
                          },
                          "dropCap": {
                            "default": false,
                            "description": "Render the first letter as an editorial drop cap.",
                            "type": "boolean"
                          }
                        },
                        "required": [
                          "body",
                          "dropCap"
                        ],
                        "additionalProperties": false,
                        "description": "Free-form paragraph. Use for narratives, intros, callouts."
                      }
                    },
                    "required": [
                      "type",
                      "id",
                      "visible",
                      "content"
                    ],
                    "additionalProperties": false,
                    "description": "Single paragraph block, optional drop cap."
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "const": "prose-card"
                      },
                      "id": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 64,
                        "pattern": "^[a-z][a-z0-9-]*$",
                        "description": "Stable id (kebab-case). EditOps target blocks by id - never rename an id once a block is referenced by saved data."
                      },
                      "visible": {
                        "default": true,
                        "description": "Show or hide the block on the rendered page.",
                        "type": "boolean"
                      },
                      "style": {
                        "type": "object",
                        "properties": {
                          "bg": {
                            "description": "Block background colour. Hex (#rrggbb), rgb(), or CSS keyword.",
                            "type": "string",
                            "maxLength": 40
                          },
                          "fg": {
                            "description": "Body text colour for this block.",
                            "type": "string",
                            "maxLength": 40
                          },
                          "accent": {
                            "description": "Accent colour for highlights inside the block (numbers, pills, borders).",
                            "type": "string",
                            "maxLength": 40
                          },
                          "bodyFont": {
                            "description": "CSS font-family stack OR Google Fonts family name for body text.",
                            "type": "string",
                            "maxLength": 120
                          },
                          "headingFont": {
                            "description": "CSS font-family stack OR Google Fonts family name for headings.",
                            "type": "string",
                            "maxLength": 120
                          },
                          "paddingY": {
                            "type": "string",
                            "enum": [
                              "none",
                              "compact",
                              "comfy",
                              "spacious"
                            ],
                            "description": "Vertical padding around the block. 'none' = flush against neighbours; 'spacious' = generous breathing room."
                          },
                          "align": {
                            "type": "string",
                            "enum": [
                              "left",
                              "center",
                              "right"
                            ],
                            "description": "Text alignment inside the block."
                          },
                          "maxWidth": {
                            "type": "string",
                            "enum": [
                              "narrow",
                              "default",
                              "wide",
                              "full"
                            ],
                            "description": "Constrain block width. 'narrow' = readable column (~640px), 'wide' = full content area, 'full' = bleed to edges."
                          }
                        },
                        "additionalProperties": false,
                        "description": "Per-block style override. Page-level theme fills in any omitted key."
                      },
                      "content": {
                        "type": "object",
                        "properties": {
                          "heading": {
                            "description": "Card heading / eyebrow.",
                            "type": "string",
                            "maxLength": 120
                          },
                          "body": {
                            "type": "object",
                            "properties": {
                              "source": {
                                "default": "outcome",
                                "description": "Where the value comes from. 'outcome' = bind to a field on the AI outcome (see outcomePath). 'static' = use the literal `text` below.",
                                "type": "string",
                                "enum": [
                                  "outcome",
                                  "static"
                                ]
                              },
                              "outcomePath": {
                                "description": "Dot-path into the outcome (e.g. 'archetype', 'score', 'traits[0]'). Required when source='outcome'. Renderer falls back to `text` if path is missing.",
                                "type": "string",
                                "maxLength": 80
                              },
                              "text": {
                                "description": "Literal text. Used when source='static' or as a fallback if the bound outcome field is absent.",
                                "type": "string",
                                "maxLength": 400
                              }
                            },
                            "required": [
                              "source"
                            ],
                            "additionalProperties": false,
                            "description": "Card body text. Often bound to a section by id (outcomePath='sections.<id>.body')."
                          },
                          "accentBar": {
                            "default": true,
                            "description": "Render a vertical accent stripe on the card's left edge.",
                            "type": "boolean"
                          }
                        },
                        "required": [
                          "body",
                          "accentBar"
                        ],
                        "additionalProperties": false,
                        "description": "A single bordered card containing prose."
                      }
                    },
                    "required": [
                      "type",
                      "id",
                      "visible",
                      "content"
                    ],
                    "additionalProperties": false,
                    "description": "One bordered card. Compose multiple to build sectioned reports."
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "const": "prose-card-row"
                      },
                      "id": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 64,
                        "pattern": "^[a-z][a-z0-9-]*$",
                        "description": "Stable id (kebab-case). EditOps target blocks by id - never rename an id once a block is referenced by saved data."
                      },
                      "visible": {
                        "default": true,
                        "description": "Show or hide the block on the rendered page.",
                        "type": "boolean"
                      },
                      "style": {
                        "type": "object",
                        "properties": {
                          "bg": {
                            "description": "Block background colour. Hex (#rrggbb), rgb(), or CSS keyword.",
                            "type": "string",
                            "maxLength": 40
                          },
                          "fg": {
                            "description": "Body text colour for this block.",
                            "type": "string",
                            "maxLength": 40
                          },
                          "accent": {
                            "description": "Accent colour for highlights inside the block (numbers, pills, borders).",
                            "type": "string",
                            "maxLength": 40
                          },
                          "bodyFont": {
                            "description": "CSS font-family stack OR Google Fonts family name for body text.",
                            "type": "string",
                            "maxLength": 120
                          },
                          "headingFont": {
                            "description": "CSS font-family stack OR Google Fonts family name for headings.",
                            "type": "string",
                            "maxLength": 120
                          },
                          "paddingY": {
                            "type": "string",
                            "enum": [
                              "none",
                              "compact",
                              "comfy",
                              "spacious"
                            ],
                            "description": "Vertical padding around the block. 'none' = flush against neighbours; 'spacious' = generous breathing room."
                          },
                          "align": {
                            "type": "string",
                            "enum": [
                              "left",
                              "center",
                              "right"
                            ],
                            "description": "Text alignment inside the block."
                          },
                          "maxWidth": {
                            "type": "string",
                            "enum": [
                              "narrow",
                              "default",
                              "wide",
                              "full"
                            ],
                            "description": "Constrain block width. 'narrow' = readable column (~640px), 'wide' = full content area, 'full' = bleed to edges."
                          }
                        },
                        "additionalProperties": false,
                        "description": "Per-block style override. Page-level theme fills in any omitted key."
                      },
                      "content": {
                        "type": "object",
                        "properties": {
                          "columns": {
                            "default": 3,
                            "type": "integer",
                            "minimum": 2,
                            "maximum": 4
                          },
                          "cards": {
                            "minItems": 2,
                            "maxItems": 4,
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "heading": {
                                  "type": "string",
                                  "maxLength": 120
                                },
                                "body": {
                                  "type": "string",
                                  "maxLength": 800
                                },
                                "accent": {
                                  "description": "Optional accent colour override for this card only.",
                                  "type": "string",
                                  "maxLength": 40
                                }
                              },
                              "required": [
                                "heading",
                                "body"
                              ],
                              "additionalProperties": false
                            },
                            "description": "2-4 cards rendered side by side."
                          }
                        },
                        "required": [
                          "columns",
                          "cards"
                        ],
                        "additionalProperties": false,
                        "description": "A row of 2-4 sibling cards (the Sifter '3-up cards' pattern)."
                      }
                    },
                    "required": [
                      "type",
                      "id",
                      "visible",
                      "content"
                    ],
                    "additionalProperties": false,
                    "description": "Compact multi-card row. AI uses this for short paired insights."
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "const": "pull-quote"
                      },
                      "id": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 64,
                        "pattern": "^[a-z][a-z0-9-]*$",
                        "description": "Stable id (kebab-case). EditOps target blocks by id - never rename an id once a block is referenced by saved data."
                      },
                      "visible": {
                        "default": true,
                        "description": "Show or hide the block on the rendered page.",
                        "type": "boolean"
                      },
                      "style": {
                        "type": "object",
                        "properties": {
                          "bg": {
                            "description": "Block background colour. Hex (#rrggbb), rgb(), or CSS keyword.",
                            "type": "string",
                            "maxLength": 40
                          },
                          "fg": {
                            "description": "Body text colour for this block.",
                            "type": "string",
                            "maxLength": 40
                          },
                          "accent": {
                            "description": "Accent colour for highlights inside the block (numbers, pills, borders).",
                            "type": "string",
                            "maxLength": 40
                          },
                          "bodyFont": {
                            "description": "CSS font-family stack OR Google Fonts family name for body text.",
                            "type": "string",
                            "maxLength": 120
                          },
                          "headingFont": {
                            "description": "CSS font-family stack OR Google Fonts family name for headings.",
                            "type": "string",
                            "maxLength": 120
                          },
                          "paddingY": {
                            "type": "string",
                            "enum": [
                              "none",
                              "compact",
                              "comfy",
                              "spacious"
                            ],
                            "description": "Vertical padding around the block. 'none' = flush against neighbours; 'spacious' = generous breathing room."
                          },
                          "align": {
                            "type": "string",
                            "enum": [
                              "left",
                              "center",
                              "right"
                            ],
                            "description": "Text alignment inside the block."
                          },
                          "maxWidth": {
                            "type": "string",
                            "enum": [
                              "narrow",
                              "default",
                              "wide",
                              "full"
                            ],
                            "description": "Constrain block width. 'narrow' = readable column (~640px), 'wide' = full content area, 'full' = bleed to edges."
                          }
                        },
                        "additionalProperties": false,
                        "description": "Per-block style override. Page-level theme fills in any omitted key."
                      },
                      "content": {
                        "type": "object",
                        "properties": {
                          "quote": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 400
                          },
                          "cite": {
                            "type": "string",
                            "maxLength": 80
                          }
                        },
                        "required": [
                          "quote"
                        ],
                        "additionalProperties": false,
                        "description": "Editorial pull quote. Often rendered italic with a side rule."
                      }
                    },
                    "required": [
                      "type",
                      "id",
                      "visible",
                      "content"
                    ],
                    "additionalProperties": false,
                    "description": "A quotable line. Magazine template uses this prominently."
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "const": "action-list"
                      },
                      "id": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 64,
                        "pattern": "^[a-z][a-z0-9-]*$",
                        "description": "Stable id (kebab-case). EditOps target blocks by id - never rename an id once a block is referenced by saved data."
                      },
                      "visible": {
                        "default": true,
                        "description": "Show or hide the block on the rendered page.",
                        "type": "boolean"
                      },
                      "style": {
                        "type": "object",
                        "properties": {
                          "bg": {
                            "description": "Block background colour. Hex (#rrggbb), rgb(), or CSS keyword.",
                            "type": "string",
                            "maxLength": 40
                          },
                          "fg": {
                            "description": "Body text colour for this block.",
                            "type": "string",
                            "maxLength": 40
                          },
                          "accent": {
                            "description": "Accent colour for highlights inside the block (numbers, pills, borders).",
                            "type": "string",
                            "maxLength": 40
                          },
                          "bodyFont": {
                            "description": "CSS font-family stack OR Google Fonts family name for body text.",
                            "type": "string",
                            "maxLength": 120
                          },
                          "headingFont": {
                            "description": "CSS font-family stack OR Google Fonts family name for headings.",
                            "type": "string",
                            "maxLength": 120
                          },
                          "paddingY": {
                            "type": "string",
                            "enum": [
                              "none",
                              "compact",
                              "comfy",
                              "spacious"
                            ],
                            "description": "Vertical padding around the block. 'none' = flush against neighbours; 'spacious' = generous breathing room."
                          },
                          "align": {
                            "type": "string",
                            "enum": [
                              "left",
                              "center",
                              "right"
                            ],
                            "description": "Text alignment inside the block."
                          },
                          "maxWidth": {
                            "type": "string",
                            "enum": [
                              "narrow",
                              "default",
                              "wide",
                              "full"
                            ],
                            "description": "Constrain block width. 'narrow' = readable column (~640px), 'wide' = full content area, 'full' = bleed to edges."
                          }
                        },
                        "additionalProperties": false,
                        "description": "Per-block style override. Page-level theme fills in any omitted key."
                      },
                      "content": {
                        "type": "object",
                        "properties": {
                          "heading": {
                            "default": "Here's what to do next",
                            "description": "Heading above the list.",
                            "type": "string",
                            "maxLength": 120
                          },
                          "steps": {
                            "minItems": 1,
                            "maxItems": 6,
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "title": {
                                  "type": "string",
                                  "maxLength": 120
                                },
                                "sub": {
                                  "type": "string",
                                  "maxLength": 240
                                },
                                "done": {
                                  "default": false,
                                  "type": "boolean"
                                },
                                "href": {
                                  "type": "string",
                                  "maxLength": 500
                                }
                              },
                              "required": [
                                "title",
                                "done"
                              ],
                              "additionalProperties": false
                            },
                            "description": "1-6 numbered next-step rows."
                          }
                        },
                        "required": [
                          "heading",
                          "steps"
                        ],
                        "additionalProperties": false,
                        "description": "Numbered next-step list. Pairs with the Action Plan template."
                      }
                    },
                    "required": [
                      "type",
                      "id",
                      "visible",
                      "content"
                    ],
                    "additionalProperties": false,
                    "description": "A numbered to-do list of recommended actions. Best for onboarding and lead-nurture."
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "const": "tile-grid"
                      },
                      "id": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 64,
                        "pattern": "^[a-z][a-z0-9-]*$",
                        "description": "Stable id (kebab-case). EditOps target blocks by id - never rename an id once a block is referenced by saved data."
                      },
                      "visible": {
                        "default": true,
                        "description": "Show or hide the block on the rendered page.",
                        "type": "boolean"
                      },
                      "style": {
                        "type": "object",
                        "properties": {
                          "bg": {
                            "description": "Block background colour. Hex (#rrggbb), rgb(), or CSS keyword.",
                            "type": "string",
                            "maxLength": 40
                          },
                          "fg": {
                            "description": "Body text colour for this block.",
                            "type": "string",
                            "maxLength": 40
                          },
                          "accent": {
                            "description": "Accent colour for highlights inside the block (numbers, pills, borders).",
                            "type": "string",
                            "maxLength": 40
                          },
                          "bodyFont": {
                            "description": "CSS font-family stack OR Google Fonts family name for body text.",
                            "type": "string",
                            "maxLength": 120
                          },
                          "headingFont": {
                            "description": "CSS font-family stack OR Google Fonts family name for headings.",
                            "type": "string",
                            "maxLength": 120
                          },
                          "paddingY": {
                            "type": "string",
                            "enum": [
                              "none",
                              "compact",
                              "comfy",
                              "spacious"
                            ],
                            "description": "Vertical padding around the block. 'none' = flush against neighbours; 'spacious' = generous breathing room."
                          },
                          "align": {
                            "type": "string",
                            "enum": [
                              "left",
                              "center",
                              "right"
                            ],
                            "description": "Text alignment inside the block."
                          },
                          "maxWidth": {
                            "type": "string",
                            "enum": [
                              "narrow",
                              "default",
                              "wide",
                              "full"
                            ],
                            "description": "Constrain block width. 'narrow' = readable column (~640px), 'wide' = full content area, 'full' = bleed to edges."
                          }
                        },
                        "additionalProperties": false,
                        "description": "Per-block style override. Page-level theme fills in any omitted key."
                      },
                      "content": {
                        "type": "object",
                        "properties": {
                          "columns": {
                            "default": 3,
                            "type": "integer",
                            "minimum": 2,
                            "maximum": 4
                          },
                          "tiles": {
                            "minItems": 2,
                            "maxItems": 12,
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "label": {
                                  "type": "string",
                                  "maxLength": 60
                                },
                                "value": {
                                  "type": "string",
                                  "maxLength": 60,
                                  "description": "Headline value (often a number)."
                                },
                                "sub": {
                                  "description": "Small line beneath the value.",
                                  "type": "string",
                                  "maxLength": 120
                                },
                                "accent": {
                                  "type": "string",
                                  "maxLength": 40
                                }
                              },
                              "required": [
                                "label",
                                "value"
                              ],
                              "additionalProperties": false
                            },
                            "description": "Tile entries, one per cell."
                          }
                        },
                        "required": [
                          "columns",
                          "tiles"
                        ],
                        "additionalProperties": false,
                        "description": "Grid of stat tiles - the Dashboard template's main pattern."
                      }
                    },
                    "required": [
                      "type",
                      "id",
                      "visible",
                      "content"
                    ],
                    "additionalProperties": false,
                    "description": "Stat-tile grid. Use to surface multiple metrics in one glance."
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "const": "cta"
                      },
                      "id": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 64,
                        "pattern": "^[a-z][a-z0-9-]*$",
                        "description": "Stable id (kebab-case). EditOps target blocks by id - never rename an id once a block is referenced by saved data."
                      },
                      "visible": {
                        "default": true,
                        "description": "Show or hide the block on the rendered page.",
                        "type": "boolean"
                      },
                      "style": {
                        "type": "object",
                        "properties": {
                          "bg": {
                            "description": "Block background colour. Hex (#rrggbb), rgb(), or CSS keyword.",
                            "type": "string",
                            "maxLength": 40
                          },
                          "fg": {
                            "description": "Body text colour for this block.",
                            "type": "string",
                            "maxLength": 40
                          },
                          "accent": {
                            "description": "Accent colour for highlights inside the block (numbers, pills, borders).",
                            "type": "string",
                            "maxLength": 40
                          },
                          "bodyFont": {
                            "description": "CSS font-family stack OR Google Fonts family name for body text.",
                            "type": "string",
                            "maxLength": 120
                          },
                          "headingFont": {
                            "description": "CSS font-family stack OR Google Fonts family name for headings.",
                            "type": "string",
                            "maxLength": 120
                          },
                          "paddingY": {
                            "type": "string",
                            "enum": [
                              "none",
                              "compact",
                              "comfy",
                              "spacious"
                            ],
                            "description": "Vertical padding around the block. 'none' = flush against neighbours; 'spacious' = generous breathing room."
                          },
                          "align": {
                            "type": "string",
                            "enum": [
                              "left",
                              "center",
                              "right"
                            ],
                            "description": "Text alignment inside the block."
                          },
                          "maxWidth": {
                            "type": "string",
                            "enum": [
                              "narrow",
                              "default",
                              "wide",
                              "full"
                            ],
                            "description": "Constrain block width. 'narrow' = readable column (~640px), 'wide' = full content area, 'full' = bleed to edges."
                          }
                        },
                        "additionalProperties": false,
                        "description": "Per-block style override. Page-level theme fills in any omitted key."
                      },
                      "content": {
                        "type": "object",
                        "properties": {
                          "primaryLabel": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 60
                          },
                          "primaryUrl": {
                            "type": "string",
                            "maxLength": 500
                          },
                          "primaryStyle": {
                            "default": "solid",
                            "type": "string",
                            "enum": [
                              "solid",
                              "outline",
                              "ghost"
                            ]
                          },
                          "secondaryLabel": {
                            "type": "string",
                            "maxLength": 60
                          },
                          "secondaryUrl": {
                            "type": "string",
                            "maxLength": 500
                          },
                          "secondaryStyle": {
                            "default": "outline",
                            "type": "string",
                            "enum": [
                              "solid",
                              "outline",
                              "ghost"
                            ]
                          },
                          "note": {
                            "description": "Small italic line next to the buttons.",
                            "type": "string",
                            "maxLength": 160
                          }
                        },
                        "required": [
                          "primaryLabel",
                          "primaryUrl",
                          "primaryStyle",
                          "secondaryStyle"
                        ],
                        "additionalProperties": false,
                        "description": "Call-to-action button row. Up to two buttons + a note."
                      }
                    },
                    "required": [
                      "type",
                      "id",
                      "visible",
                      "content"
                    ],
                    "additionalProperties": false,
                    "description": "Primary action affordance. Place at top OR bottom of the page."
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "const": "share-row"
                      },
                      "id": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 64,
                        "pattern": "^[a-z][a-z0-9-]*$",
                        "description": "Stable id (kebab-case). EditOps target blocks by id - never rename an id once a block is referenced by saved data."
                      },
                      "visible": {
                        "default": true,
                        "description": "Show or hide the block on the rendered page.",
                        "type": "boolean"
                      },
                      "style": {
                        "type": "object",
                        "properties": {
                          "bg": {
                            "description": "Block background colour. Hex (#rrggbb), rgb(), or CSS keyword.",
                            "type": "string",
                            "maxLength": 40
                          },
                          "fg": {
                            "description": "Body text colour for this block.",
                            "type": "string",
                            "maxLength": 40
                          },
                          "accent": {
                            "description": "Accent colour for highlights inside the block (numbers, pills, borders).",
                            "type": "string",
                            "maxLength": 40
                          },
                          "bodyFont": {
                            "description": "CSS font-family stack OR Google Fonts family name for body text.",
                            "type": "string",
                            "maxLength": 120
                          },
                          "headingFont": {
                            "description": "CSS font-family stack OR Google Fonts family name for headings.",
                            "type": "string",
                            "maxLength": 120
                          },
                          "paddingY": {
                            "type": "string",
                            "enum": [
                              "none",
                              "compact",
                              "comfy",
                              "spacious"
                            ],
                            "description": "Vertical padding around the block. 'none' = flush against neighbours; 'spacious' = generous breathing room."
                          },
                          "align": {
                            "type": "string",
                            "enum": [
                              "left",
                              "center",
                              "right"
                            ],
                            "description": "Text alignment inside the block."
                          },
                          "maxWidth": {
                            "type": "string",
                            "enum": [
                              "narrow",
                              "default",
                              "wide",
                              "full"
                            ],
                            "description": "Constrain block width. 'narrow' = readable column (~640px), 'wide' = full content area, 'full' = bleed to edges."
                          }
                        },
                        "additionalProperties": false,
                        "description": "Per-block style override. Page-level theme fills in any omitted key."
                      },
                      "content": {
                        "type": "object",
                        "properties": {
                          "providers": {
                            "minItems": 1,
                            "maxItems": 6,
                            "type": "array",
                            "items": {
                              "type": "string",
                              "enum": [
                                "twitter",
                                "linkedin",
                                "whatsapp",
                                "facebook",
                                "email",
                                "copy"
                              ]
                            },
                            "description": "Which share targets to render."
                          },
                          "label": {
                            "default": "Share your result",
                            "description": "Eyebrow label above the row.",
                            "type": "string",
                            "maxLength": 80
                          }
                        },
                        "required": [
                          "providers",
                          "label"
                        ],
                        "additionalProperties": false,
                        "description": "Social share buttons. Bound to the form's deep-link share URL."
                      }
                    },
                    "required": [
                      "type",
                      "id",
                      "visible",
                      "content"
                    ],
                    "additionalProperties": false,
                    "description": "Share row. Lifts virality on personality / quiz forms."
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "const": "image"
                      },
                      "id": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 64,
                        "pattern": "^[a-z][a-z0-9-]*$",
                        "description": "Stable id (kebab-case). EditOps target blocks by id - never rename an id once a block is referenced by saved data."
                      },
                      "visible": {
                        "default": true,
                        "description": "Show or hide the block on the rendered page.",
                        "type": "boolean"
                      },
                      "style": {
                        "type": "object",
                        "properties": {
                          "bg": {
                            "description": "Block background colour. Hex (#rrggbb), rgb(), or CSS keyword.",
                            "type": "string",
                            "maxLength": 40
                          },
                          "fg": {
                            "description": "Body text colour for this block.",
                            "type": "string",
                            "maxLength": 40
                          },
                          "accent": {
                            "description": "Accent colour for highlights inside the block (numbers, pills, borders).",
                            "type": "string",
                            "maxLength": 40
                          },
                          "bodyFont": {
                            "description": "CSS font-family stack OR Google Fonts family name for body text.",
                            "type": "string",
                            "maxLength": 120
                          },
                          "headingFont": {
                            "description": "CSS font-family stack OR Google Fonts family name for headings.",
                            "type": "string",
                            "maxLength": 120
                          },
                          "paddingY": {
                            "type": "string",
                            "enum": [
                              "none",
                              "compact",
                              "comfy",
                              "spacious"
                            ],
                            "description": "Vertical padding around the block. 'none' = flush against neighbours; 'spacious' = generous breathing room."
                          },
                          "align": {
                            "type": "string",
                            "enum": [
                              "left",
                              "center",
                              "right"
                            ],
                            "description": "Text alignment inside the block."
                          },
                          "maxWidth": {
                            "type": "string",
                            "enum": [
                              "narrow",
                              "default",
                              "wide",
                              "full"
                            ],
                            "description": "Constrain block width. 'narrow' = readable column (~640px), 'wide' = full content area, 'full' = bleed to edges."
                          }
                        },
                        "additionalProperties": false,
                        "description": "Per-block style override. Page-level theme fills in any omitted key."
                      },
                      "content": {
                        "type": "object",
                        "properties": {
                          "url": {
                            "type": "string",
                            "maxLength": 2048
                          },
                          "alt": {
                            "default": "",
                            "type": "string",
                            "maxLength": 200
                          },
                          "caption": {
                            "type": "string",
                            "maxLength": 240
                          },
                          "position": {
                            "default": "full",
                            "type": "string",
                            "enum": [
                              "full",
                              "centered",
                              "left",
                              "right"
                            ]
                          }
                        },
                        "required": [
                          "url",
                          "alt",
                          "position"
                        ],
                        "additionalProperties": false,
                        "description": "Standalone image with optional caption."
                      }
                    },
                    "required": [
                      "type",
                      "id",
                      "visible",
                      "content"
                    ],
                    "additionalProperties": false,
                    "description": "Image block. Sourced from the workspace file library or any URL."
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "const": "divider"
                      },
                      "id": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 64,
                        "pattern": "^[a-z][a-z0-9-]*$",
                        "description": "Stable id (kebab-case). EditOps target blocks by id - never rename an id once a block is referenced by saved data."
                      },
                      "visible": {
                        "default": true,
                        "description": "Show or hide the block on the rendered page.",
                        "type": "boolean"
                      },
                      "style": {
                        "type": "object",
                        "properties": {
                          "bg": {
                            "description": "Block background colour. Hex (#rrggbb), rgb(), or CSS keyword.",
                            "type": "string",
                            "maxLength": 40
                          },
                          "fg": {
                            "description": "Body text colour for this block.",
                            "type": "string",
                            "maxLength": 40
                          },
                          "accent": {
                            "description": "Accent colour for highlights inside the block (numbers, pills, borders).",
                            "type": "string",
                            "maxLength": 40
                          },
                          "bodyFont": {
                            "description": "CSS font-family stack OR Google Fonts family name for body text.",
                            "type": "string",
                            "maxLength": 120
                          },
                          "headingFont": {
                            "description": "CSS font-family stack OR Google Fonts family name for headings.",
                            "type": "string",
                            "maxLength": 120
                          },
                          "paddingY": {
                            "type": "string",
                            "enum": [
                              "none",
                              "compact",
                              "comfy",
                              "spacious"
                            ],
                            "description": "Vertical padding around the block. 'none' = flush against neighbours; 'spacious' = generous breathing room."
                          },
                          "align": {
                            "type": "string",
                            "enum": [
                              "left",
                              "center",
                              "right"
                            ],
                            "description": "Text alignment inside the block."
                          },
                          "maxWidth": {
                            "type": "string",
                            "enum": [
                              "narrow",
                              "default",
                              "wide",
                              "full"
                            ],
                            "description": "Constrain block width. 'narrow' = readable column (~640px), 'wide' = full content area, 'full' = bleed to edges."
                          }
                        },
                        "additionalProperties": false,
                        "description": "Per-block style override. Page-level theme fills in any omitted key."
                      },
                      "content": {
                        "type": "object",
                        "properties": {
                          "kind": {
                            "default": "line",
                            "type": "string",
                            "enum": [
                              "line",
                              "dots",
                              "space"
                            ]
                          },
                          "spacing": {
                            "default": "comfy",
                            "type": "string",
                            "enum": [
                              "none",
                              "compact",
                              "comfy",
                              "spacious"
                            ],
                            "description": "Vertical padding around the block. 'none' = flush against neighbours; 'spacious' = generous breathing room."
                          }
                        },
                        "required": [
                          "kind",
                          "spacing"
                        ],
                        "additionalProperties": false,
                        "description": "Visual separator between sections."
                      }
                    },
                    "required": [
                      "type",
                      "id",
                      "visible",
                      "content"
                    ],
                    "additionalProperties": false,
                    "description": "Horizontal rule or pure whitespace. Use to control rhythm."
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "const": "custom-html"
                      },
                      "id": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 64,
                        "pattern": "^[a-z][a-z0-9-]*$",
                        "description": "Stable id (kebab-case). EditOps target blocks by id - never rename an id once a block is referenced by saved data."
                      },
                      "visible": {
                        "default": true,
                        "description": "Show or hide the block on the rendered page.",
                        "type": "boolean"
                      },
                      "style": {
                        "type": "object",
                        "properties": {
                          "bg": {
                            "description": "Block background colour. Hex (#rrggbb), rgb(), or CSS keyword.",
                            "type": "string",
                            "maxLength": 40
                          },
                          "fg": {
                            "description": "Body text colour for this block.",
                            "type": "string",
                            "maxLength": 40
                          },
                          "accent": {
                            "description": "Accent colour for highlights inside the block (numbers, pills, borders).",
                            "type": "string",
                            "maxLength": 40
                          },
                          "bodyFont": {
                            "description": "CSS font-family stack OR Google Fonts family name for body text.",
                            "type": "string",
                            "maxLength": 120
                          },
                          "headingFont": {
                            "description": "CSS font-family stack OR Google Fonts family name for headings.",
                            "type": "string",
                            "maxLength": 120
                          },
                          "paddingY": {
                            "type": "string",
                            "enum": [
                              "none",
                              "compact",
                              "comfy",
                              "spacious"
                            ],
                            "description": "Vertical padding around the block. 'none' = flush against neighbours; 'spacious' = generous breathing room."
                          },
                          "align": {
                            "type": "string",
                            "enum": [
                              "left",
                              "center",
                              "right"
                            ],
                            "description": "Text alignment inside the block."
                          },
                          "maxWidth": {
                            "type": "string",
                            "enum": [
                              "narrow",
                              "default",
                              "wide",
                              "full"
                            ],
                            "description": "Constrain block width. 'narrow' = readable column (~640px), 'wide' = full content area, 'full' = bleed to edges."
                          }
                        },
                        "additionalProperties": false,
                        "description": "Per-block style override. Page-level theme fills in any omitted key."
                      },
                      "content": {
                        "type": "object",
                        "properties": {
                          "html": {
                            "type": "string",
                            "maxLength": 4000,
                            "description": "Raw HTML (sanitized on render - script tags + event handlers stripped). Power-user escape hatch."
                          }
                        },
                        "required": [
                          "html"
                        ],
                        "additionalProperties": false,
                        "description": "Sanitized HTML escape hatch."
                      }
                    },
                    "required": [
                      "type",
                      "id",
                      "visible",
                      "content"
                    ],
                    "additionalProperties": false,
                    "description": "Custom HTML block. Sanitizer removes <script>, on* attributes, and javascript: URLs."
                  }
                ],
                "description": "One block on the response page. Add via add_block, patch via update_block, delete via remove_block, reorder via move_block. Page-level theme via set_response_theme."
              }
            },
            "theme": {
              "default": {},
              "description": "Page-level theme used as the fallback for every block. Per-block `style` overrides win.",
              "type": "object",
              "properties": {
                "bg": {
                  "description": "Page background colour.",
                  "type": "string",
                  "maxLength": 40
                },
                "fg": {
                  "description": "Default body text colour.",
                  "type": "string",
                  "maxLength": 40
                },
                "accent": {
                  "description": "Primary accent used for buttons, numbers, focus rings.",
                  "type": "string",
                  "maxLength": 40
                },
                "cardBg": {
                  "description": "Card / panel background colour.",
                  "type": "string",
                  "maxLength": 40
                },
                "border": {
                  "description": "Default border colour for cards and dividers.",
                  "type": "string",
                  "maxLength": 40
                },
                "bodyFont": {
                  "description": "Default body font (CSS family stack or Google Fonts name).",
                  "type": "string",
                  "maxLength": 120
                },
                "headingFont": {
                  "description": "Default heading font (CSS family stack or Google Fonts name).",
                  "type": "string",
                  "maxLength": 120
                },
                "headingWeight": {
                  "description": "Default heading weight.",
                  "type": "string",
                  "enum": [
                    "normal",
                    "medium",
                    "semibold",
                    "bold",
                    "black"
                  ]
                },
                "radius": {
                  "description": "Border-radius value applied to cards (e.g. '4px', '12px', '9999px').",
                  "type": "string",
                  "maxLength": 20
                },
                "maxWidth": {
                  "description": "Default content max-width. Individual blocks can override.",
                  "type": "string",
                  "enum": [
                    "narrow",
                    "default",
                    "wide"
                  ]
                }
              },
              "additionalProperties": false
            },
            "showScore": {
              "default": "auto",
              "description": "Score visibility policy. 'auto' = show only when intelligence.outputMode === 'scored'. 'never' suppresses the score even on scored forms; 'always' forces it on profile forms (with a 0 default).",
              "type": "string",
              "enum": [
                "auto",
                "always",
                "never"
              ]
            }
          },
          "required": [
            "title",
            "subtitle",
            "body",
            "showFormLogo",
            "showFormTitle",
            "background",
            "backgroundColor",
            "confetti",
            "heroImage",
            "backgroundImage",
            "shape",
            "spacing",
            "accentColor",
            "cardStyle",
            "cardColor",
            "cta",
            "secondaryCta",
            "share",
            "redirect",
            "branding",
            "customCss",
            "template",
            "blocks",
            "theme",
            "showScore"
          ],
          "additionalProperties": false
        },
        "collectEmail": {
          "default": false,
          "description": "If true, the respondent's email is requested and stored.",
          "type": "boolean"
        },
        "allowMultipleSubmissions": {
          "default": true,
          "description": "If false, a respondent may submit only once.",
          "type": "boolean"
        },
        "shape": {
          "default": "one_per_page",
          "description": "Form layout. 'one_per_page' (default) shows one question at a time with Next/Back navigation - focused, conversational, the Typeform pattern. 'single_page' shows every question on one scrolling page (better for research surveys or short forms where respondents want to skim).",
          "type": "string",
          "enum": [
            "single_page",
            "one_per_page"
          ]
        },
        "width": {
          "default": "full",
          "description": "Page width. 'full' (default) spans the screen. 'wide' = ~1080px (data-entry, multi-column feel). 'centered' = ~640px readable column. 'narrow' = ~520px (short text-heavy forms).",
          "type": "string",
          "enum": [
            "narrow",
            "centered",
            "wide",
            "full"
          ]
        },
        "size": {
          "default": "large",
          "description": "Type scale. Multiplier applied to title, body, helper text, chip text, input text together (0.9 / 1.0 / 1.2 / 1.4). 'large' (default) is the sweet spot for full-screen one-per-page forms; small for power-user data entry; xl for kiosks.",
          "type": "string",
          "enum": [
            "small",
            "standard",
            "large",
            "xl"
          ]
        },
        "density": {
          "default": "comfortable",
          "description": "Spacing scale. Multiplier on gap + padding between question blocks and around inputs (0.7 / 1.0 / 1.4). Compact = research surveys with many fields; spacious = single-call kiosks.",
          "type": "string",
          "enum": [
            "compact",
            "comfortable",
            "spacious"
          ]
        },
        "notifyEmail": {
          "description": "If set, a notification email is sent to this address on each new response.",
          "type": "string",
          "format": "email",
          "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
        },
        "scoring": {
          "description": "Optional quiz/scoring behaviour.",
          "type": "object",
          "properties": {
            "enabled": {
              "default": false,
              "description": "Turn the form into a graded quiz.",
              "type": "boolean"
            },
            "showScore": {
              "default": true,
              "description": "Show the respondent their score after submitting.",
              "type": "boolean"
            },
            "outcomes": {
              "description": "Optional score-range outcomes; the first matching range's message replaces the confirmation message.",
              "maxItems": 20,
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "minScore": {
                    "description": "Inclusive lower bound of the score range.",
                    "type": "number"
                  },
                  "maxScore": {
                    "description": "Inclusive upper bound of the score range.",
                    "type": "number"
                  },
                  "message": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 2000,
                    "description": "Message shown when the score falls in this range."
                  }
                },
                "required": [
                  "message"
                ],
                "additionalProperties": false
              }
            }
          },
          "required": [
            "enabled",
            "showScore"
          ],
          "additionalProperties": false
        },
        "coverImage": {
          "description": "Optional hero image rendered above the form title (welcome banner).",
          "type": "object",
          "properties": {
            "url": {
              "type": "string",
              "maxLength": 2048,
              "format": "uri",
              "description": "Image URL. In the builder UI this is always a file from the workspace library."
            },
            "alt": {
              "description": "Accessible alt text.",
              "type": "string",
              "maxLength": 200
            },
            "position": {
              "description": "Layout relative to the question (top = default; left/right place image side-by-side with the question).",
              "type": "string",
              "enum": [
                "top",
                "bottom",
                "left",
                "right"
              ]
            },
            "focalX": {
              "description": "Horizontal focal point as a 0..1 fraction (default 0.5).",
              "type": "number",
              "minimum": 0,
              "maximum": 1
            },
            "focalY": {
              "description": "Vertical focal point as a 0..1 fraction (default 0.5).",
              "type": "number",
              "minimum": 0,
              "maximum": 1
            },
            "brightness": {
              "description": "Brightness offset as a percent (-100..100); 0 = unchanged. Useful for hero/cover images sitting under text.",
              "type": "number",
              "minimum": -100,
              "maximum": 100
            }
          },
          "required": [
            "url"
          ],
          "additionalProperties": false
        },
        "stickers": {
          "description": "Decorative images placed freely on the form (drag/resize/rotate). Never block input.",
          "maxItems": 24,
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 64,
                "pattern": "^[a-z0-9_-]+$",
                "description": "Stable sticker id, unique within the form."
              },
              "url": {
                "type": "string",
                "maxLength": 2048,
                "format": "uri",
                "description": "Image URL - a file from the workspace library."
              },
              "alt": {
                "description": "Accessible alt text; omit/empty for purely decorative.",
                "type": "string",
                "maxLength": 200
              },
              "x": {
                "type": "number",
                "minimum": 0,
                "maximum": 100,
                "description": "Centre X, percent of form width."
              },
              "y": {
                "type": "number",
                "minimum": 0,
                "maximum": 100,
                "description": "Centre Y, percent of form height."
              },
              "width": {
                "type": "number",
                "minimum": 2,
                "maximum": 100,
                "description": "Width as a percent of the form width."
              },
              "rotate": {
                "description": "Rotation in degrees (-180..180).",
                "type": "number",
                "minimum": -180,
                "maximum": 180
              }
            },
            "required": [
              "id",
              "url",
              "x",
              "y",
              "width"
            ],
            "additionalProperties": false,
            "description": "A free-placed decorative image overlaid on the form."
          }
        },
        "antiSpam": {
          "description": "Anti-spam knobs the form owner can flip. Honeypot + per-IP rate limit are always on at the database; this block is for opt-in extras (Turnstile today).",
          "type": "object",
          "properties": {
            "turnstileEnabled": {
              "description": "When true, the public form renders a Cloudflare Turnstile widget and the server rejects submissions without a valid token. Honeypot + per-IP rate limit are always on; this adds an interactive challenge for forms that face active abuse.",
              "type": "boolean"
            }
          },
          "additionalProperties": false
        },
        "consentBanner": {
          "description": "Respondent privacy / consent banner. Default ON. Disables the banner without disabling the data collection: when disabled, no extras are collected (so toggling off is the safe choice for already-consented host sites).",
          "type": "object",
          "properties": {
            "enabled": {
              "default": true,
              "description": "When true (default), the public form renders a floating consent banner offering 'Accept all' (collect extended device + geo metadata) vs 'Necessary only' (no extras). The respondent's choice is stored in the askery_consent cookie for one year. Turn off when the form is embedded inside a site that already runs its own consent banner - Askery will then collect ONLY the minimal anti-spam metadata.",
              "type": "boolean"
            }
          },
          "required": [
            "enabled"
          ],
          "additionalProperties": false
        },
        "integrations": {
          "description": "Per-form analytics trackers + workspace-default opt-outs. Tracker ids override the workspace default when set; *Enabled=false suppresses an inherited default; disabledWebhookIds lets a form skip specific workspace-wide webhooks.",
          "type": "object",
          "properties": {
            "gtmId": {
              "description": "Google Tag Manager container id, e.g. GTM-ABC123.",
              "type": "string",
              "maxLength": 40,
              "pattern": "^GTM-[A-Z0-9]+$"
            },
            "metaPixelId": {
              "description": "Meta (Facebook) Pixel id - digits only.",
              "type": "string",
              "maxLength": 32,
              "pattern": "^\\d+$"
            },
            "ga4Id": {
              "description": "Google Analytics 4 Measurement ID, e.g. G-XXXXXXX.",
              "type": "string",
              "maxLength": 20,
              "pattern": "^G-[A-Z0-9]+$"
            },
            "clarityId": {
              "description": "Microsoft Clarity project id.",
              "type": "string",
              "maxLength": 20,
              "pattern": "^[a-z0-9]+$"
            },
            "gtmEnabled": {
              "description": "If false, this form opts out of the workspace GTM default. Undefined = inherit.",
              "type": "boolean"
            },
            "metaPixelEnabled": {
              "description": "If false, this form opts out of the workspace Meta Pixel default.",
              "type": "boolean"
            },
            "ga4Enabled": {
              "description": "If false, this form opts out of the workspace GA4 default.",
              "type": "boolean"
            },
            "clarityEnabled": {
              "description": "If false, this form opts out of the workspace Clarity default.",
              "type": "boolean"
            },
            "disabledWebhookIds": {
              "description": "Workspace-wide webhook ids this form skips. Empty/undefined = inherit all workspace hooks.",
              "maxItems": 64,
              "type": "array",
              "items": {
                "type": "string",
                "format": "uuid",
                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
              }
            }
          },
          "additionalProperties": false
        },
        "intelligence": {
          "description": "Form Intelligence - the result page rendered after submit. Pick ONE mode: `smart_rules` (LLM, qualitative) or `decision_engine` (server-side code, deterministic). Empty / disabled by default; the form falls back to the static confirmation message.",
          "type": "object",
          "properties": {
            "enabled": {
              "default": false,
              "description": "If true, submit redirects the respondent to /f/<slug>/result/<session>; the configured `mode` decides how the result is computed.",
              "type": "boolean"
            },
            "mode": {
              "default": "smart_rules",
              "description": "Which engine fills the result. `smart_rules` (default): an LLM interprets `rules` against the answers - qualitative, narrative. `decision_engine`: server-side code in `code` runs against the answers in a QuickJS-emscripten WASM isolate - deterministic, programmable, ideal for weighted scoring and DB lookups. The two modes are mutually exclusive per form.",
              "type": "string",
              "enum": [
                "smart_rules",
                "decision_engine"
              ]
            },
            "rules": {
              "default": "",
              "description": "[smart_rules mode] Plain-language evaluation criteria - the form owner's logic for picking results. Used as the LLM's authoritative instructions. May include tables, IF/THEN mappings, scoring dimensions, tone guidance.",
              "type": "string",
              "maxLength": 8000
            },
            "code": {
              "default": "",
              "description": "[decision_engine mode] JavaScript source (the constrained Decision Engine DSL) that runs against each submission. Must export a default function `decide({ answers, score, recommend, tier, output })`. Executes in a QuickJS-emscripten WASM isolate: 5s CPU, 128MB RAM, no network, no FS. AI-generated from a brief; can be edited in the Code view or rendered as blocks in the Rules view.",
              "type": "string",
              "maxLength": 200000
            },
            "sections": {
              "default": [],
              "description": "The shape of the result page - each section becomes one card the engine fills in.",
              "maxItems": 12,
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64,
                    "pattern": "^[a-z0-9_-]+$",
                    "description": "Stable section id. Becomes a key in the engine's JSON output."
                  },
                  "title": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120,
                    "description": "Heading shown to the respondent (e.g. 'Best Countries For You')."
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "list",
                      "label",
                      "prose",
                      "table"
                    ],
                    "description": "list = 3-8 short strings; label = one short string; prose = a paragraph; table = rows + columns (Decision Engine only - Smart Rules doesn't emit tables)."
                  },
                  "hint": {
                    "description": "Optional guidance for the engine about this specific section (e.g. format like 'Low/Medium/High' or 'X-Y LPA').",
                    "type": "string",
                    "maxLength": 400
                  }
                },
                "required": [
                  "id",
                  "title",
                  "kind"
                ],
                "additionalProperties": false
              }
            },
            "testPersonas": {
              "default": [],
              "description": "[decision_engine mode] Generated test personas - owner-only synthetic profiles used in the builder's Test pane. Persisted with the form so they survive reloads; not surfaced to respondents.",
              "maxItems": 20,
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "label": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 80,
                    "description": "Short distinguishable name (3-6 words) shown on the persona chip."
                  },
                  "description": {
                    "description": "One-sentence summary of the persona's distinguishing trait.",
                    "type": "string",
                    "maxLength": 400
                  },
                  "answers": {
                    "type": "object",
                    "propertyNames": {
                      "type": "string"
                    },
                    "additionalProperties": {},
                    "description": "Sample answers keyed by question id, matching the form's input types. Pasted into the test pane to validate the Decision Engine code against a realistic profile."
                  }
                },
                "required": [
                  "label",
                  "answers"
                ],
                "additionalProperties": false
              }
            },
            "connectorHooks": {
              "default": [],
              "description": "Server-side connector calls that fire between questions. Each hook's response is stored under `ctx.<storeAs>` and is available to later `visibleIf` rules + Decision Engine code. Use this to push the form's answers to a customer endpoint and pull back a quote, risk tier, eligibility decision, etc.",
              "maxItems": 20,
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "afterQuestionId": {
                    "type": "string",
                    "minLength": 1,
                    "description": "The connector fires after this question is answered. Use 'form_start' to fire before the first question."
                  },
                  "connectorId": {
                    "type": "string",
                    "minLength": 1,
                    "description": "Must match a Connector id at the form's `connectors[]` array."
                  },
                  "storeAs": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 40,
                    "pattern": "^[a-z_][a-z0-9_]*$",
                    "description": "The connector's extracted output is merged into `ctx.<storeAs>`. Later questions can use `visibleIf: \"ctx.quote.tier === 'enterprise'\"`."
                  }
                },
                "required": [
                  "afterQuestionId",
                  "connectorId",
                  "storeAs"
                ],
                "additionalProperties": false,
                "description": "Server-side fire-and-store: after a question is answered, the connector runs and its result is stashed in `ctx` for later visibility/branching logic and Decision Engine code."
              }
            },
            "outputMode": {
              "default": "auto",
              "description": "Whether the engine should produce a numeric overall score. 'auto' = the codegen heuristic decides (descriptive/preference forms → profile, evaluative/quiz forms → scored). 'scored' = always produce a 0-100 score + dimensions. 'profile' = never produce a score; output is archetype + traits + sections only. Set this BEFORE regenerating the engine so generated code matches.",
              "type": "string",
              "enum": [
                "auto",
                "scored",
                "profile"
              ]
            }
          },
          "required": [
            "enabled",
            "mode",
            "rules",
            "code",
            "sections",
            "testPersonas",
            "connectorHooks",
            "outputMode"
          ],
          "additionalProperties": false
        },
        "accessControl": {
          "description": "Access-control rules. Omitted on legacy forms; backend defaults to `public` when not present.",
          "type": "object",
          "properties": {
            "mode": {
              "default": "public",
              "description": "`public` = no auth (default). `verified_google` = respondent must verify via Google. `verified_email` = respondent receives a 6-digit code at any email + enters it. `verified_either` = respondent picks Google OR code.",
              "type": "string",
              "enum": [
                "public",
                "verified_google",
                "verified_email",
                "verified_either"
              ]
            },
            "onePerEmail": {
              "default": true,
              "description": "When the form is gated, enforces one submission per verified email. Backed by a partial unique index on responses - duplicates are rejected at insert time.",
              "type": "boolean"
            }
          },
          "required": [
            "mode",
            "onePerEmail"
          ],
          "additionalProperties": false
        }
      },
      "required": [
        "submitButtonLabel",
        "confirmationMessage",
        "responsePage",
        "collectEmail",
        "allowMultipleSubmissions",
        "shape",
        "width",
        "size",
        "density"
      ],
      "additionalProperties": false,
      "description": "Form-wide behaviour settings."
    },
    "connectors": {
      "default": [],
      "description": "Reusable HTTP calls the form can make at fill time. A select-type question references one via `optionsConnector` to load dynamic options; the Intelligence config references one via `connectorHooks` to push form state and store the response under `ctx.<storeAs>` for branching logic.",
      "maxItems": 50,
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64,
            "pattern": "^[a-z0-9_-]+$",
            "description": "Stable id (kebab-case). Used by `optionsConnector.connectorId` on questions and by `connectorHooks[].connectorId` in intelligence settings."
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80,
            "description": "Human label shown in the builder. Example: 'Cities lookup'."
          },
          "description": {
            "description": "Owner-supplied description. Surfaced to the AI so it can choose the right connector when the operator says 'use the cities API'.",
            "type": "string",
            "maxLength": 400
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "HTTPS endpoint. SSRF-guarded at invoke time (private IPs and link-local hostnames are blocked)."
          },
          "method": {
            "default": "POST",
            "type": "string",
            "enum": [
              "GET",
              "POST",
              "PUT",
              "PATCH"
            ],
            "description": "HTTP method. Most APIs are GET (for lookups) or POST (for stateful calls)."
          },
          "headers": {
            "type": "object",
            "propertyNames": {
              "type": "string",
              "minLength": 1,
              "maxLength": 80,
              "pattern": "^[A-Za-z0-9-]+$"
            },
            "additionalProperties": {
              "type": "string",
              "maxLength": 2000
            },
            "description": "Custom request headers. Values support `{{secret.NAME}}` interpolation (the secret lives in workspace_secrets and is never round-tripped through the API)."
          },
          "bodyTemplate": {
            "description": "JSON body. String values support `{{answers.<q_id>}}` and `{{secret.NAME}}` substitution. For GET requests this is ignored - use `queryTemplate` instead."
          },
          "queryTemplate": {
            "description": "Querystring key/values, also templated. Becomes `?k1=v1&k2=v2` appended to the URL.",
            "type": "object",
            "propertyNames": {
              "type": "string",
              "minLength": 1
            },
            "additionalProperties": {
              "type": "string",
              "maxLength": 2000
            }
          },
          "responseAs": {
            "default": "options",
            "description": "How to consume the response. `options` (default) extracts an array and shapes it into `{value,label}[]`. `context` merges the extracted object into `ctx.<storeAs>` for later questions / DE code.",
            "type": "string",
            "enum": [
              "options",
              "context"
            ]
          },
          "outputPath": {
            "description": "Dot-path into the response body. Example: `data.cities`. Default = use the whole body. Lookups support `[index]` (e.g. `data.tiers[0]`).",
            "type": "string",
            "maxLength": 200
          },
          "optionLabel": {
            "description": "Dot-path inside each option item. Default `label`. Falls back to `name` then the raw value if absent.",
            "type": "string",
            "maxLength": 120
          },
          "optionValue": {
            "description": "Dot-path inside each option item. Default `id`. Falls back to `value` then the raw item if absent.",
            "type": "string",
            "maxLength": 120
          },
          "optionImage": {
            "description": "Image URL TEMPLATE for picture_choice options. `{{item.<field>}}` tokens are substituted from each option item, so you can build a URL from an id even when the API returns no image field - e.g. `https://cdn.example.com/products/{{item.id}}.jpg`. Use `{{item.<field>}}` to read a direct image field too (e.g. `{{item.primary_image}}`). Resolves to empty -> that option renders label-only (graceful for ragged catalogs). Only consumed when the question is a picture_choice.",
            "type": "string",
            "maxLength": 500
          },
          "maxResults": {
            "description": "Cap on the number of options/items returned to the respondent (applied AFTER outputPath extraction). Useful when the customer's endpoint returns thousands of rows but the dropdown only needs the first N. For real filtering, push it to the source via bodyTemplate/queryTemplate.",
            "type": "integer",
            "minimum": 1,
            "maximum": 2000
          },
          "cacheTtlSeconds": {
            "default": 60,
            "description": "Cache the response per (workspace, connector, body-fingerprint) for this many seconds. 0 disables. Default 60.",
            "type": "integer",
            "minimum": 0,
            "maximum": 3600
          },
          "timeoutMs": {
            "default": 5000,
            "description": "Per-attempt wall clock cap. After this, we abort and (if `retryAttempts > 0`) retry.",
            "type": "integer",
            "minimum": 100,
            "maximum": 15000
          },
          "retryAttempts": {
            "default": 2,
            "description": "Number of retries on network or 5xx error. Total attempts = 1 + retryAttempts.",
            "type": "integer",
            "minimum": 0,
            "maximum": 5
          },
          "fallbackOnError": {
            "default": false,
            "description": "If true, a connector failure during option load is treated as 'no options available' (the user can still submit a free-text fallback). If false, the question blocks submission until it succeeds.",
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "name",
          "url",
          "method",
          "responseAs",
          "cacheTtlSeconds",
          "timeoutMs",
          "retryAttempts",
          "fallbackOnError"
        ],
        "additionalProperties": false,
        "description": "Reusable HTTP call. The same connector can back a dropdown's options AND a branching hook - choose by `responseAs`."
      }
    }
  },
  "required": [
    "schemaVersion",
    "title",
    "questions",
    "settings",
    "connectors"
  ],
  "additionalProperties": false,
  "description": "Canonical form definition. The single contract for storing, rendering, and LLM-authoring a form.",
  "title": "Form"
}
