{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://raw.githubusercontent.com/Soleone/showcase/main/schema/showcase.schema.json",
  "title": "showcase.json",
  "description": "Facts about one project that a portfolio can show. Importance and ordering belong to the portfolio, not here.",
  "type": "object",
  "required": ["title", "pitch", "description", "kind"],
  "additionalProperties": false,
  "patternProperties": {
    "^x-": {}
  },
  "properties": {
    "$schema": { "type": "string" },
    "title": { "type": "string", "minLength": 1, "maxLength": 60 },
    "pitch": {
      "type": "string",
      "minLength": 1,
      "maxLength": 160,
      "description": "One sentence that makes someone want to click. A subtitle: short enough to sit under a title."
    },
    "description": {
      "type": "string",
      "minLength": 1,
      "maxLength": 240,
      "description": "A couple of sentences that go further than the pitch (how it works, what makes it different). Never restates the pitch; a portfolio shows the two together."
    },
    "kind": {
      "enum": ["web-app", "local-web-app", "cli", "library", "browser-extension", "desktop-app", "agent-extension", "game", "service", "theme", "other"],
      "description": "local-web-app: a browser UI backed by a process on the user's machine (local models, hardware, files), so it cannot be hosted as-is."
    },
    "stage": {
      "enum": ["product", "prototype", "proof of concept"],
      "description": "How far along it is. product: meant to be used; prototype: a working draft of an idea; proof of concept: shows it can be done."
    },
    "year": { "type": "integer", "minimum": 1990, "maximum": 2100 },
    "tech": { "type": "array", "items": { "type": "string", "minLength": 1 } },
    "repo": { "type": "string", "format": "uri" },
    "live": {
      "type": "string",
      "format": "uri",
      "description": "One click to try it for real: a hosted demo, browser terminal, store listing, playground or replay."
    },
    "media": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "hero": { "type": "string", "minLength": 1, "description": "Still image, path relative to the project root or a URL." },
        "motion": { "type": "string", "minLength": 1, "description": "Short silent loop (animated WebP or GIF)." },
        "video": { "type": "string", "minLength": 1, "description": "Longer video, may have sound (MP4 or WebM)." },
        "screenshots": { "type": "array", "items": { "type": "string", "minLength": 1 } },
        "capture": { "type": "string", "minLength": 1, "description": "Command that regenerates the media from scratch." }
      }
    },
    "brand": {
      "type": "object",
      "additionalProperties": false,
      "description": "How the project looks as itself, for a favicon, a card or a project page. Not graded.",
      "properties": {
        "icon": { "type": "string", "minLength": 1, "description": "Square mark that works from 16 to 512 px (SVG preferred). The favicon source. Path relative to the project root or a URL." },
        "logo": { "type": "string", "minLength": 1, "description": "Full lockup (icon and name) for wide spots such as a project page header." },
        "wordmark": { "type": "string", "minLength": 1, "description": "The name set in its own lettering, without the icon." },
        "colors": {
          "type": "array",
          "items": { "type": "string", "pattern": "^#[0-9a-fA-F]{6}$" },
          "description": "Brand colors as #rrggbb, primary first. A portfolio tints a card with the first."
        }
      }
    },
    "demo": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "strategy": {
          "enum": ["hosted", "browser-terminal", "store-listing", "playground", "replay", "download", "none"]
        },
        "local": { "type": "string", "minLength": 1, "description": "One command that runs a seeded demo locally." },
        "externals": {
          "type": "array",
          "description": "What the project needs that a demo cannot reach (models, accounts, hardware, services, other apps), and the stand-in the demo uses instead. An empty list says there are none.",
          "items": {
            "type": "object",
            "required": ["name", "type", "seam", "standIn"],
            "additionalProperties": false,
            "properties": {
              "name": { "type": "string", "minLength": 1, "description": "What it is, e.g. \"LLM summaries\"." },
              "type": { "enum": ["model", "service", "account", "hardware", "app", "data"] },
              "seam": { "type": "string", "minLength": 1, "description": "Where the stand-in plugs in without changing the project's code: a setting, env var, flag or injected client." },
              "standIn": {
                "enum": ["canned", "replay", "simulated", "bring-your-own", "real", "omitted"],
                "description": "canned: fixed responses keyed by input. replay: recorded real responses. simulated: a fake implementation. bring-your-own: the visitor supplies a key or account. real: the demo uses the real thing. omitted: the feature is off in the demo."
              },
              "note": { "type": "string", "minLength": 1, "description": "One sentence for visitors, e.g. \"Summaries are canned; nothing is sent to a model.\"" }
            }
          }
        }
      }
    },
    "ceiling": {
      "enum": ["D", "C", "B", "A", "S"],
      "description": "Highest tier this project can realistically reach, e.g. A when it cannot be hosted."
    }
  }
}
