{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://openpresentation.org/schema/opf-narrative/v1",
  "title": "Narrative Template",
  "description": "Schema for narrative template files in the openpresentation.org catalog. Each template describes a named story arc (e.g. 'problem-solution', 'scqa') as an ordered list of beats. Templates are referenced from OPF documents via narrative — either as a bare id string (e.g. 'classic-story') or as an inline object whose shape matches this schema (sans '$schema').",
  "type": "object",
  "required": [
    "$schema",
    "id",
    "name",
    "beats"
  ],
  "properties": {
    "$schema": {
      "type": "string",
      "const": "https://openpresentation.org/schema/opf-narrative/v1"
    },
    "id": {
      "type": "string",
      "pattern": "^[a-z][a-z0-9-]*$",
      "description": "Stable slug used by OPF documents to reference this template, e.g. 'problem-solution'. Lowercase kebab-case."
    },
    "name": {
      "type": "string",
      "description": "Human-readable template name, e.g. 'Problem → Solution'."
    },
    "summary": {
      "type": "string",
      "description": "One-sentence description of when and why to use this narrative."
    },
    "description": {
      "type": "string",
      "description": "Longer prose describing the narrative arc and ideal use cases. Used by AI-driven generation to seed deck-level direction.",
      "examples": [
        "Open with the cost of slow agentic workflows, contrast with what becomes possible at sub-second latency, then walk through our architecture and benchmark results, ending with a concrete adoption ask."
      ]
    },
    "audienceFit": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Audiences this narrative works well for, e.g. ['executives', 'investors', 'customers']."
    },
    "durationRange": {
      "type": "object",
      "description": "Typical talk-length window this narrative suits.",
      "properties": {
        "minMinutes": {
          "type": "number",
          "exclusiveMinimum": 0
        },
        "maxMinutes": {
          "type": "number",
          "exclusiveMinimum": 0
        }
      }
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Free-form labels for filtering and search, e.g. ['business', 'pitch', 'internal']."
    },
    "preview": {
      "type": "object",
      "description": "Visual previews of the record, used by picker UIs and inline rendering. All sub-fields are optional; engines fall back gracefully when previews aren't available.",
      "properties": {
        "src": {
          "type": "string",
          "format": "uri",
          "description": "Main preview image (PNG/JPG). Used as the primary visual in picker UIs and previews of the record.",
          "examples": [
            "https://www.pptx.gallery/narratives/classic-story.png"
          ]
        },
        "thumbnailSrc": {
          "type": "string",
          "format": "uri",
          "description": "Smaller thumbnail preview suited to dense grid views.",
          "examples": [
            "https://www.pptx.gallery/narratives/classic-story.thumbnail.png"
          ]
        },
        "vectorSrc": {
          "type": "string",
          "format": "uri",
          "description": "SVG / vector preview for crisp scaling at any size.",
          "examples": [
            "https://www.pptx.gallery/narratives/classic-story.svg"
          ]
        }
      }
    },
    "beats": {
      "type": "array",
      "minItems": 1,
      "items": {
        "$ref": "#/$defs/Beat"
      },
      "description": "Ordered list of beats that make up the narrative arc."
    }
  },
  "$defs": {
    "Beat": {
      "type": "object",
      "description": "A single narrative beat — a labeled segment of the story arc with a specific dramatic purpose. Mirrors the NarrativeBeat definition in opf.schema.json so library entries and inline OPF beats are interchangeable.",
      "required": [
        "id",
        "name"
      ],
      "properties": {
        "id": {
          "type": "string",
          "pattern": "^[a-z][a-z0-9-]*$",
          "description": "Stable slug used by Slide.beat to reference this beat. Lowercase kebab-case.",
          "examples": [
            "opening",
            "problem",
            "evidence",
            "ask",
            "next-steps"
          ]
        },
        "name": {
          "type": "string",
          "description": "Human-readable beat name, e.g. 'The Problem'.",
          "examples": [
            "Opening",
            "The Problem",
            "Why Now",
            "The Ask"
          ]
        },
        "description": {
          "type": "string",
          "description": "Curator-written prose that explains what this beat should accomplish.",
          "examples": [
            "Quantify the pain customers feel today, with one striking stat the audience can repeat afterward."
          ]
        },
        "instructions": {
          "type": "string",
          "description": "Short author-facing instruction for the beat — typically one phrase. Complements 'description' with a concise directive.",
          "examples": [
            "Capture audience attention",
            "Introduce problem",
            "Detail implementation",
            "Inspire & conclude"
          ]
        },
        "slideCount": {
          "type": "integer",
          "minimum": 1,
          "description": "Optional explicit slide count for this beat. Defaults to 1 when omitted; values >1 are reserved for beats that intentionally span multiple slides. Prefer decomposing a heavy beat into multiple beats over setting a high slideCount. The validator emits a warning if the deck's actual count differs significantly.",
          "examples": [
            1,
            2,
            3
          ]
        },
        "slideType": {
          "type": "string",
          "enum": [
            "text",
            "list",
            "image",
            "shape",
            "chart",
            "table",
            "video",
            "code",
            "metric",
            "quote",
            "timeline"
          ],
          "description": "Default content kind for the beat's slide. Uses ContentPayload.type names to help engines choose a layout. The legacy shape value is retained for compatibility and requests an image representation; it is not a native ContentPayload type."
        },
        "layoutHint": {
          "type": "string",
          "description": "Suggested layout id for the beat's opening slide, e.g. 'section-divider', 'title-slide', 'text-left'. Resolves the same way as Slide.layout — against catalogs.layouts and the default catalog at https://www.pptx.gallery/layouts.",
          "examples": [
            "section-divider",
            "title-slide",
            "title-left",
            "two-column",
            "text-left"
          ]
        },
        "thoughtCues": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Optional speaker or thinking cues attached to the beat. Surfaced in presenter notes.",
          "examples": [
            [
              "What pain is the audience feeling right now?",
              "Why hasn't anyone solved this yet?"
            ]
          ]
        }
      }
    }
  }
}
