{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://project.np.design/pitch.schema.json",
  "title": "Client pitch",
  "type": "object",
  "additionalProperties": false,
  "required": ["slug", "title", "theme", "contact", "slides"],
  "properties": {
    "slug": {
      "type": "string",
      "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
      "description": "Folder name and URL path, such as desa-alam."
    },
    "title": { "type": "string", "minLength": 1 },
    "lang": { "type": "string", "minLength": 1, "default": "en" },
    "password": {
      "type": "string",
      "minLength": 1,
      "description": "Optional visual gate. Omit it to leave the deck open. The check runs in the browser, so the value is visible in the page source."
    },
    "gate": {
      "type": "object",
      "additionalProperties": false,
      "description": "Optional password-gate copy. Omitted fields use the English defaults: Password, Open, Incorrect password.",
      "properties": {
        "label": { "type": "string", "minLength": 1 },
        "button": { "type": "string", "minLength": 1 },
        "error": { "type": "string", "minLength": 1 }
      }
    },
    "noindex": {
      "type": "boolean",
      "description": "When true, the page asks search engines not to index it."
    },
    "theme": {
      "type": "object",
      "additionalProperties": false,
      "required": ["pageBackground", "light", "dark", "fonts"],
      "properties": {
        "pageBackground": { "$ref": "#/$defs/hex" },
        "light": { "$ref": "#/$defs/palette" },
        "dark": { "$ref": "#/$defs/palette" },
        "fonts": {
          "type": "object",
          "additionalProperties": false,
          "required": ["sans", "serif"],
          "properties": {
            "sans": { "$ref": "#/$defs/font" },
            "serif": { "$ref": "#/$defs/font" }
          }
        }
      }
    },
    "contact": {
      "type": "object",
      "additionalProperties": false,
      "required": ["name", "email", "site", "siteLabel"],
      "properties": {
        "name": { "type": "string", "minLength": 1 },
        "email": { "type": "string", "format": "email" },
        "site": { "type": "string", "pattern": "^https://" },
        "siteLabel": { "type": "string", "minLength": 1 }
      }
    },
    "slides": {
      "type": "array",
      "minItems": 8,
      "maxItems": 8,
      "prefixItems": [
        { "$ref": "#/$defs/cover" },
        { "$ref": "#/$defs/overview" },
        { "$ref": "#/$defs/cards" },
        { "$ref": "#/$defs/list" },
        { "$ref": "#/$defs/list" },
        { "$ref": "#/$defs/process" },
        { "$ref": "#/$defs/cost" },
        { "$ref": "#/$defs/closing" }
      ]
    }
  },
  "$defs": {
    "hex": { "type": "string", "pattern": "^#[0-9A-Fa-f]{6}$" },
    "color": {
      "description": "Solid #RRGGBB, or rgba() when a line needs opacity. Hex decks render as before.",
      "oneOf": [
        { "$ref": "#/$defs/hex" },
        {
          "type": "string",
          "pattern": "^rgba\\(\\s*(?:25[0-5]|2[0-4]\\d|1?\\d{1,2})\\s*,\\s*(?:25[0-5]|2[0-4]\\d|1?\\d{1,2})\\s*,\\s*(?:25[0-5]|2[0-4]\\d|1?\\d{1,2})\\s*,\\s*(?:0|1|0?\\.\\d+)\\s*\\)$"
        }
      ]
    },
    "text": { "type": "string", "minLength": 1 },
    "palette": {
      "type": "object",
      "additionalProperties": false,
      "required": ["bgPrimary", "bgSecondary", "contentPrimary", "contentSecondary", "contentSubtle", "borderPrimary", "borderSecondary"],
      "properties": {
        "bgPrimary": { "$ref": "#/$defs/color" },
        "bgSecondary": { "$ref": "#/$defs/color" },
        "contentPrimary": { "$ref": "#/$defs/color" },
        "contentSecondary": { "$ref": "#/$defs/color" },
        "contentSubtle": { "$ref": "#/$defs/color" },
        "borderPrimary": { "$ref": "#/$defs/color" },
        "borderSecondary": { "$ref": "#/$defs/color" }
      }
    },
    "font": {
      "type": "object",
      "additionalProperties": false,
      "required": ["family", "fallback"],
      "anyOf": [
        { "required": ["google"] },
        { "required": ["files"] }
      ],
      "properties": {
        "family": { "type": "string", "description": "Font family name, such as Jost or TT Commons Pro." },
        "google": { "type": "string", "description": "Google Fonts axis spec after the family name, such as wght@200;300;400. Omit it when files supplies the family." },
        "files": {
          "type": "array",
          "minItems": 1,
          "description": "Self-hosted faces. When present, this family is not requested from Google Fonts.",
          "items": { "$ref": "#/$defs/fontFile" }
        },
        "fallback": { "enum": ["sans-serif", "serif"] }
      }
    },
    "fontFile": {
      "type": "object",
      "additionalProperties": false,
      "required": ["weight", "src"],
      "properties": {
        "weight": { "type": "integer", "minimum": 1, "maximum": 1000 },
        "style": { "enum": ["normal", "italic"], "default": "normal" },
        "src": {
          "description": "Path inside the client folder, or a list of paths. woff2 is listed before woff.",
          "oneOf": [
            { "type": "string", "minLength": 1 },
            { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 } }
          ]
        }
      }
    },
    "decorations": {
      "type": "array",
      "minItems": 1,
      "maxItems": 2,
      "description": "Optional soft background shapes, at most two, behind the slide text.",
      "items": { "$ref": "#/$defs/decoration" }
    },
    "decoration": {
      "type": "object",
      "additionalProperties": false,
      "required": ["src", "position"],
      "properties": {
        "src": { "type": "string", "minLength": 1, "description": "Path inside the client folder, such as assets/halo.svg." },
        "position": { "enum": ["top-left", "top-right", "bottom-left", "bottom-right", "left", "right"] },
        "size": { "type": "string", "pattern": "^\\d+(?:\\.\\d+)?(?:px|vw|vh|vmin|vmax|%)$" },
        "opacity": { "type": "number", "minimum": 0, "maximum": 1 }
      }
    },
    "logo": {
      "type": "object",
      "additionalProperties": false,
      "required": ["src", "alt", "width", "height"],
      "properties": {
        "src": { "type": "string", "description": "Path inside the client folder, such as assets/logo.png." },
        "alt": { "$ref": "#/$defs/text" },
        "width": { "type": "integer", "minimum": 1 },
        "height": { "type": "integer", "minimum": 1 },
        "blend": { "enum": ["lighten", "normal"], "description": "Cover logo blend. Defaults to lighten, which knocks a dark plate out of a light mark. Use normal for a dark mark on a light slide." }
      }
    },
    "cover": {
      "type": "object",
      "additionalProperties": false,
      "required": ["type", "eyebrow", "title", "subtitle"],
      "properties": {
        "type": { "const": "cover" },
        "eyebrow": { "$ref": "#/$defs/text" },
        "title": { "$ref": "#/$defs/text" },
        "subtitle": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/text" } },
        "logo": { "$ref": "#/$defs/logo" },
        "decorations": { "$ref": "#/$defs/decorations" },
        "theme": { "enum": ["light", "dark"], "description": "Overrides the default light or dark slide. Cover and closing are dark unless set to light." },
        "background": { "$ref": "#/$defs/hex", "description": "Optional solid slide background. Text colors still come from the slide theme." },
        "backgroundImage": { "type": "string", "minLength": 1, "description": "Optional full-bleed image inside the client folder, such as assets/background.svg. Drawn with object-fit cover behind the slide text." },
        "contentSecondary": { "$ref": "#/$defs/color", "description": "Optional override for secondary text on this slide, such as the eyebrow and subtitle." },
        "contentSubtle": { "$ref": "#/$defs/color", "description": "Optional override for subtle text on this slide, such as the contact lines." }
      }
    },
    "overview": {
      "type": "object",
      "additionalProperties": false,
      "required": ["type", "label", "title", "lede"],
      "properties": {
        "type": { "const": "overview" },
        "label": { "$ref": "#/$defs/text" },
        "title": { "$ref": "#/$defs/text" },
        "lede": { "$ref": "#/$defs/text" }
      }
    },
    "cardItem": {
      "type": "object",
      "additionalProperties": false,
      "required": ["title", "points"],
      "properties": {
        "title": { "$ref": "#/$defs/text" },
        "points": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/text" } }
      }
    },
    "cards": {
      "type": "object",
      "additionalProperties": false,
      "required": ["type", "label", "title", "items"],
      "properties": {
        "type": { "const": "cards" },
        "label": { "$ref": "#/$defs/text" },
        "title": { "$ref": "#/$defs/text" },
        "items": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/cardItem" } }
      }
    },
    "list": {
      "type": "object",
      "additionalProperties": false,
      "required": ["type", "label", "title", "rows"],
      "properties": {
        "type": { "const": "list" },
        "label": { "$ref": "#/$defs/text" },
        "title": { "$ref": "#/$defs/text" },
        "keyWidth": {
          "type": "string",
          "pattern": "^\\d+(?:\\.\\d+)?(?:px|rem|em|ch)$",
          "description": "Optional desktop width of the label column, such as 18rem. Omitted labels keep the shared minimum width. Below 480px the column stacks as usual."
        },
        "rows": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["key", "value"],
            "properties": {
              "key": {
                "description": "Row label. A newline breaks the label onto another line.",
                "allOf": [{ "$ref": "#/$defs/text" }]
              },
              "value": { "$ref": "#/$defs/text" }
            }
          }
        }
      }
    },
    "process": {
      "type": "object",
      "additionalProperties": false,
      "required": ["type", "label", "title", "items", "launch"],
      "properties": {
        "type": { "const": "process" },
        "label": { "$ref": "#/$defs/text" },
        "title": { "$ref": "#/$defs/text" },
        "launch": { "$ref": "#/$defs/text" },
        "items": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["title", "days", "points"],
            "properties": {
              "title": { "$ref": "#/$defs/text" },
              "days": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/text" } },
              "points": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/text" } }
            }
          }
        }
      }
    },
    "cost": {
      "type": "object",
      "additionalProperties": false,
      "required": ["type", "label", "title", "equation", "amount", "currency", "hosting"],
      "properties": {
        "type": { "const": "cost" },
        "label": { "$ref": "#/$defs/text" },
        "title": { "$ref": "#/$defs/text" },
        "equation": { "$ref": "#/$defs/text" },
        "amount": { "$ref": "#/$defs/text" },
        "currency": { "$ref": "#/$defs/text" },
        "hosting": {
          "type": "object",
          "additionalProperties": false,
          "required": ["label", "rows", "disclaimer"],
          "properties": {
            "label": { "$ref": "#/$defs/text" },
            "disclaimer": { "$ref": "#/$defs/text" },
            "rows": {
              "type": "array",
              "minItems": 1,
              "items": {
                "type": "object",
                "additionalProperties": false,
                "required": ["name", "value"],
                "properties": {
                  "name": { "$ref": "#/$defs/text" },
                  "value": { "$ref": "#/$defs/text" }
                }
              }
            }
          }
        }
      }
    },
    "closing": {
      "type": "object",
      "additionalProperties": false,
      "required": ["type", "eyebrow", "title", "subtitle"],
      "properties": {
        "type": { "const": "closing" },
        "eyebrow": { "$ref": "#/$defs/text" },
        "title": { "$ref": "#/$defs/text" },
        "subtitle": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/text" } },
        "decorations": { "$ref": "#/$defs/decorations" },
        "theme": { "enum": ["light", "dark"] },
        "background": { "$ref": "#/$defs/hex" },
        "backgroundImage": { "type": "string", "minLength": 1, "description": "Optional full-bleed image inside the client folder, drawn with object-fit cover." },
        "contentSecondary": { "$ref": "#/$defs/color" },
        "contentSubtle": { "$ref": "#/$defs/color" }
      }
    }
  }
}
