{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://supercards.io/sef/schema.json",
  "title": "SuperCards Exchange Format (SEF) v1.1",
  "description": "Machine-readable schema for .supercards.json deck manifests (format 1.1.0). This schema is the authoring pre-flight filter: it is deliberately STRICTER than the app's import validator (canonical enum values only, no unknown fields) so that generated files use the canonical vocabulary. The app's Rust validator is the final authority at import; it additionally accepts common aliases (e.g. role 'title' -> 'heading', language 'ts' -> 'typescript'), ignores unknown fields, and skips unknown block types with a warning. Anything that passes this schema will pass the import validator, provided referenced image/PDF files actually exist. Human-readable spec: https://supercards.io/sef",
  "type": "object",
  "required": ["supercards_format", "deck", "cards"],
  "additionalProperties": false,
  "properties": {
    "supercards_format": {
      "type": "string",
      "pattern": "^1\\.[0-9]+\\.[0-9]+$",
      "description": "Format version. Write \"1.1.0\". Any 1.x file is accepted and read under 1.1 semantics; 2.x is rejected.",
      "examples": ["1.1.0"]
    },
    "deck": { "$ref": "#/$defs/deck" },
    "cards": {
      "type": "array",
      "minItems": 1,
      "maxItems": 200,
      "description": "The deck's cards, in display order (1-200; importers warn above 100). A card is a single thought: one concept per card, split rather than shrink.",
      "items": { "$ref": "#/$defs/card" }
    }
  },
  "$defs": {
    "deck": {
      "title": "Deck metadata",
      "type": "object",
      "required": ["title"],
      "additionalProperties": false,
      "properties": {
        "title": {
          "type": "string",
          "minLength": 1,
          "maxLength": 150,
          "description": "Deck display name (max 150 chars)."
        },
        "summary": {
          "type": "string",
          "description": "Short deck description shown in the library."
        },
        "color": {
          "$ref": "#/$defs/hexColor",
          "description": "Card background color applied to all cards unless a card overrides it. Default \"#F4F4F4\"."
        },
        "card_size": {
          "$ref": "#/$defs/cardSize",
          "description": "Default canvas size for all cards. Default \"medium\". Cards may override individually via card.size."
        },
        "source": {
          "type": "string",
          "description": "Provenance, e.g. \"Generated by Claude from workout.pdf\". Always fill this in."
        },
        "tags": {
          "type": "array",
          "items": { "type": "string" },
          "description": "Reserved for future use."
        }
      }
    },
    "card": {
      "title": "Card",
      "type": "object",
      "required": ["front"],
      "additionalProperties": false,
      "properties": {
        "title": {
          "type": "string",
          "description": "Optional card name shown in card management UI, not on the canvas."
        },
        "tags": {
          "type": "array",
          "items": { "type": "string" },
          "description": "Card-level tags."
        },
        "color": {
          "$ref": "#/$defs/hexColor",
          "description": "Per-card background color override."
        },
        "size": {
          "$ref": "#/$defs/cardSize",
          "description": "Per-card canvas size override of the deck default. Use sparingly - e.g. \"large\" for a face that genuinely needs width (code, wide diagrams)."
        },
        "front": {
          "type": "array",
          "minItems": 1,
          "description": "Front face content (the prompt: question, term, name, image). At least one block.",
          "items": { "$ref": "#/$defs/contentBlock" }
        },
        "back": {
          "type": "array",
          "description": "Back face content (the payload: answer, explanation, steps). Optional - a front-only card is valid for reference decks.",
          "items": { "$ref": "#/$defs/contentBlock" }
        }
      }
    },
    "contentBlock": {
      "title": "Content block",
      "description": "One block of face content. Discriminated by \"type\": text, image, or code. (\"math\" is reserved for a future version - do not emit it; importers skip unknown types with a warning.)",
      "oneOf": [
        { "$ref": "#/$defs/textBlock" },
        { "$ref": "#/$defs/imageBlock" },
        { "$ref": "#/$defs/codeBlock" }
      ]
    },
    "textBlock": {
      "title": "Text block",
      "type": "object",
      "required": ["type", "text"],
      "additionalProperties": false,
      "properties": {
        "type": { "const": "text" },
        "text": {
          "type": "string",
          "minLength": 1,
          "description": "The content. MARKDOWN BY DEFAULT: bold (**text**), italic (*text*), strikethrough (~~text~~), inline code (`text`), highlight (==text==), bullet/ordered lists (2 nesting levels max), and fenced code blocks all render. A single \\n is a hard line break; a blank line starts a new paragraph. NOT supported (normalized with a warning): # headings (use a separate block with role \"heading\"), links (text kept, URL dropped), inline images (use an image block), blockquotes, tables, raw HTML. Escape literal sigils with backslashes (\\*) or set format to \"plain\"."
        },
        "role": {
          "type": "string",
          "enum": ["heading", "subheading", "body", "caption", "label"],
          "description": "Semantic role setting the block's typographic baseline. Default \"body\". Use \"heading\" to lead a face, \"caption\" for source attributions and category labels, \"label\" for step numbers and annotations. Markdown marks compose on top of the role's baseline."
        },
        "format": {
          "type": "string",
          "enum": ["markdown", "plain"],
          "description": "Default \"markdown\" - omit the field and just write markdown. Set \"plain\" only when characters like *, _, or a leading \"1.\" must render literally (sigil-heavy notation, ASCII diagrams)."
        },
        "align": {
          "type": "string",
          "enum": ["left", "center", "right"],
          "description": "Advisory text alignment for the whole block. Default \"left\". \"center\" suits short headings and single-image faces; leave body text left-aligned."
        },
        "group": { "$ref": "#/$defs/group" }
      }
    },
    "imageBlock": {
      "title": "Image block",
      "type": "object",
      "required": ["type", "src"],
      "additionalProperties": false,
      "properties": {
        "type": { "const": "image" },
        "src": {
          "type": "string",
          "minLength": 1,
          "description": "Image reference, one of four forms: (1) bare filename bundled alongside the manifest, e.g. \"ser_conjugation.png\" (PNG/JPEG/WebP/GIF; assets/ folder preferred); (2) PDF extraction ref \"pdf:filename.pdf:page:N:index:M\" (1-based page, 0-based image index); (3) data URI \"data:image/png;base64,...\" (use sparingly); (4) direct https URL to an image you have verified exists (10 MB max, downloaded at import)."
        },
        "alt": {
          "type": "string",
          "description": "Alt text describing the image."
        },
        "size": {
          "type": "string",
          "enum": ["small", "medium", "large", "full-width"],
          "description": "Advisory width hint: small ~25-30%, medium ~40-50% (default), large ~60-75%, full-width ~90-100% of the canvas."
        },
        "group": { "$ref": "#/$defs/group" }
      }
    },
    "codeBlock": {
      "title": "Code block",
      "type": "object",
      "required": ["type", "code"],
      "additionalProperties": false,
      "properties": {
        "type": { "const": "code" },
        "code": {
          "type": "string",
          "minLength": 1,
          "description": "Source text with newlines preserved. No markdown fencing - this is raw code. Use a code block when the entire element is code (monospace layout treatment); use fenced blocks inside markdown text for mixed prose-and-code."
        },
        "language": {
          "type": "string",
          "enum": [
            "bash",
            "c",
            "cpp",
            "go",
            "javascript",
            "json",
            "markdown",
            "python",
            "rust",
            "sql",
            "swift",
            "typescript",
            "yaml"
          ],
          "description": "Syntax-highlighting language. Omit for plain text. (The import validator also normalizes common aliases: js, ts, py, c++, sh, shell, zsh, golang, jsx, tsx.)"
        },
        "group": { "$ref": "#/$defs/group" }
      }
    },
    "group": {
      "type": "string",
      "minLength": 1,
      "maxLength": 32,
      "description": "Advisory clustering label: CONSECUTIVE blocks sharing a label lay out together (e.g. a row of step photos, or a caption attached to its image). Two or three small/medium images group well; don't group more than four."
    },
    "cardSize": {
      "type": "string",
      "enum": ["small", "medium", "large"]
    },
    "hexColor": {
      "type": "string",
      "pattern": "^#[0-9A-Fa-f]{6}$",
      "description": "6-digit hex color, e.g. \"#F4F4F4\"."
    }
  }
}
