{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://downbad.farm/schemas/banner-manifest-v1.json",
  "title": "downbad Flex Machine banner manifest",
  "description": "Creator-published manifest of background-free NFT art (cutouts) and overlay art for the Banner tab of the downbad Flex Machine. See https://downbad.farm/docs/banner-studio",
  "type": "object",
  "required": ["version", "cutouts"],
  "additionalProperties": false,
  "properties": {
    "$schema": {
      "type": "string"
    },
    "version": {
      "const": 1,
      "description": "Manifest format version."
    },
    "name": {
      "type": "string",
      "maxLength": 80,
      "description": "Human-readable name for this art set, shown as the source credit."
    },
    "updated_at": {
      "type": "string",
      "format": "date-time",
      "description": "Informational RFC 3339 timestamp of the last change."
    },
    "cutouts": {
      "$ref": "#/$defs/cutouts"
    },
    "overlays": {
      "type": "array",
      "maxItems": 200,
      "items": { "$ref": "#/$defs/overlay" },
      "description": "Extra art holders can place on the canvas."
    }
  },
  "$defs": {
    "httpsUrl": {
      "type": "string",
      "format": "uri",
      "pattern": "^https://[^\\s]+$",
      "maxLength": 2048,
      "description": "Absolute https URL. Must be publicly readable and CORS-enabled."
    },
    "assetId": {
      "type": "string",
      "pattern": "^[0-9]{1,20}$",
      "description": "Algorand ASA id, as a decimal string (JSON object keys are always strings)."
    },
    "cutouts": {
      "description": "Where the background-free art for each asset lives. Discriminated on `type` rather than a bare oneOf so validators report the actual problem instead of 'not valid under any of the given schemas'.",
      "type": "object",
      "required": ["type"],
      "properties": {
        "type": { "enum": ["template", "map"] }
      },
      "allOf": [
        {
          "if": { "properties": { "type": { "const": "template" } }, "required": ["type"] },
          "then": { "$ref": "#/$defs/cutoutsTemplate" }
        },
        {
          "if": { "properties": { "type": { "const": "map" } }, "required": ["type"] },
          "then": { "$ref": "#/$defs/cutoutsMap" }
        }
      ]
    },
    "cutoutsTemplate": {
      "type": "object",
      "required": ["type", "url"],
      "additionalProperties": false,
      "properties": {
        "type": { "const": "template" },
        "url": {
          "allOf": [{ "$ref": "#/$defs/httpsUrl" }],
          "pattern": "\\{asset_id\\}",
          "description": "URL pattern containing {asset_id}, the Algorand ASA id. It is the only placeholder: asset ids are the one identifier present on every list the app already loads, so resolving anything else would cost an extra request per NFT."
        },
        "coverage": {
          "enum": ["all", "listed"],
          "default": "all",
          "description": "'all' means every asset in the collection resolves through the template. 'listed' restricts it to the ids in `available`."
        },
        "available": {
          "type": "array",
          "maxItems": 100000,
          "items": { "type": "integer", "minimum": 1 },
          "description": "Asset ids that have a cutout. Required when coverage is 'listed'."
        },
        "overrides": {
          "type": "object",
          "maxProperties": 100000,
          "propertyNames": { "$ref": "#/$defs/assetId" },
          "additionalProperties": { "$ref": "#/$defs/httpsUrl" },
          "description": "Per-asset URLs that win over the template."
        },
        "trim": {
          "type": "boolean",
          "default": true,
          "description": "Auto-crop fully transparent margins when the image loads."
        }
      },
      "allOf": [
        {
          "if": { "properties": { "coverage": { "const": "listed" } }, "required": ["coverage"] },
          "then": { "required": ["available"], "properties": { "available": { "minItems": 1 } } }
        }
      ]
    },
    "cutoutsMap": {
      "type": "object",
      "required": ["type", "assets"],
      "additionalProperties": false,
      "properties": {
        "type": { "const": "map" },
        "assets": {
          "type": "object",
          "minProperties": 1,
          "maxProperties": 100000,
          "propertyNames": { "$ref": "#/$defs/assetId" },
          "additionalProperties": { "$ref": "#/$defs/httpsUrl" },
          "description": "Explicit asset id to image URL mapping."
        },
        "trim": {
          "type": "boolean",
          "default": true
        }
      }
    },
    "overlay": {
      "type": "object",
      "required": ["id", "name", "url"],
      "additionalProperties": false,
      "properties": {
        "id": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$",
          "description": "Stable identifier, unique within the manifest."
        },
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 60,
          "description": "Label shown in the overlay picker."
        },
        "url": { "$ref": "#/$defs/httpsUrl" },
        "kind": {
          "enum": ["sticker", "background", "frame"],
          "default": "sticker",
          "description": "'sticker' free-transforms; 'background' covers the canvas behind everything; 'frame' covers the canvas in front of everything."
        },
        "group": {
          "type": "string",
          "maxLength": 40,
          "description": "Optional section heading in the picker."
        },
        "default_scale": {
          "type": "number",
          "exclusiveMinimum": 0,
          "maximum": 1,
          "default": 0.3,
          "description": "Starting width as a fraction of the canvas width. Ignored for 'background' and 'frame'."
        },
        "gate": { "$ref": "#/$defs/gate" }
      }
    },
    "gate": {
      "type": "object",
      "required": ["type"],
      "additionalProperties": false,
      "default": { "type": "holder" },
      "properties": {
        "type": {
          "enum": ["holder", "public"],
          "description": "'holder' requires at least one asset from the collection in the connected wallet. 'public' is available to everyone. Enforced client-side only — this is not a security boundary."
        }
      }
    }
  }
}
