{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://schemas.revenexx.com/theme-preset.schema.json",
  "title": "revenexx Theme Install Preset",
  "description": "Default content an editable theme ships for a tenant who installs it: pages, menus, reusable blocks and site settings. A theme lists its presets in `theme.json` under `editor.defaults`; each entry points at one file of this shape. On install the preset is seeded in fill mode: every block gets a fresh id, and only what the tenant does not have yet is added (pages by slug, menus by key, reusable blocks by label, settings by key), so the tenant's own content is never overwritten and a newer preset can be applied again later. Reset mode replaces the tenant's content with the preset and runs only on explicit request. A preset never pins a page's appearance: page host options must not carry `colorScheme`, so every tenant's brand applies on every seeded page.",
  "type": "object",
  "required": ["preset"],
  "additionalProperties": false,
  "properties": {
    "$schema": {
      "type": "string",
      "description": "Reference to this JSON Schema for editor validation and autocompletion."
    },
    "$comment": {
      "type": "string",
      "description": "Free-form note for the people maintaining the preset. Ignored when seeding."
    },
    "preset": {
      "type": "string",
      "pattern": "^[a-z][a-z0-9-]*$",
      "description": "Preset identifier. Must equal the `id` of the `editor.defaults` entry in `theme.json` that points at this file, e.g. 'standard'."
    },
    "pages": {
      "type": "array",
      "description": "Pages to create, matched by slug. A slug the tenant already has is kept as it is. Seeded pages are published immediately.",
      "items": { "$ref": "#/$defs/page" }
    },
    "menus": {
      "type": "array",
      "description": "Navigation menus to create, matched by menu key. A key the tenant already has is kept as it is.",
      "items": { "$ref": "#/$defs/menu" }
    },
    "library": {
      "type": "array",
      "description": "Reusable blocks to add to the tenant's block library, matched by label. A label the tenant already has is kept as it is.",
      "items": { "$ref": "#/$defs/libraryItem" }
    },
    "settings": {
      "type": "object",
      "description": "Site settings to set for the tenant. Each key is only written when the tenant has not set it yet.",
      "additionalProperties": false,
      "properties": {
        "design": { "$ref": "#/$defs/design" },
        "customCss": {
          "type": "string",
          "maxLength": 65536,
          "description": "Custom CSS for the whole site. The theme validates it and emits it in its own cascade layer after all theme and utility styles, so plain rules win without `!important`. `@import`, `@layer`, `@charset`, `@namespace`, `expression()`, `javascript:` and markup are rejected; `url()` must be https or same-origin; braces must balance."
        },
        "appearance": { "$ref": "#/$defs/appearance" }
      }
    }
  },
  "$defs": {
    "page": {
      "type": "object",
      "description": "One page with its block tree.",
      "required": ["slug", "title"],
      "additionalProperties": false,
      "properties": {
        "slug": {
          "type": "string",
          "minLength": 1,
          "description": "Page slug in the source language, e.g. 'home'. The key a page is matched on."
        },
        "title": {
          "type": "string",
          "minLength": 1,
          "description": "Page title in the source language."
        },
        "bundle": {
          "type": "string",
          "description": "Page type, e.g. 'standard' for content pages or 'region' for a header or footer region. Defaults to the tenant's default page type."
        },
        "sourceLanguage": {
          "type": "string",
          "description": "Language code the page is written in, e.g. 'de'. Defaults to the tenant's default source language."
        },
        "translations": {
          "type": "array",
          "description": "Further languages the page is available in.",
          "items": {
            "type": "object",
            "required": ["langcode"],
            "additionalProperties": false,
            "properties": {
              "langcode": { "type": "string", "description": "Language code, e.g. 'en'." },
              "title": { "type": "string", "description": "Page title in this language." },
              "slug": { "type": "string", "description": "Page slug in this language." }
            }
          }
        },
        "hostOptions": {
          "type": "object",
          "description": "Page-level options the theme reads, e.g. the layout mode of a region ('before', 'after' or 'replace'). Must not contain `colorScheme`: a preset never pins a page's appearance, so the tenant's brand applies on every seeded page.",
          "additionalProperties": true,
          "not": { "required": ["colorScheme"] }
        },
        "meta": {
          "type": "object",
          "description": "Page metadata such as the meta description.",
          "additionalProperties": true
        },
        "field": {
          "type": "string",
          "description": "Name of the page field the top-level blocks are placed in. Defaults to 'content'."
        },
        "blocks": {
          "type": "array",
          "description": "Top-level blocks of the page, in order.",
          "items": { "$ref": "#/$defs/block" }
        }
      }
    },
    "block": {
      "type": "object",
      "description": "One block with its nested blocks. Ids are assigned fresh on every seed, so a preset can be applied to many tenants.",
      "required": ["bundle"],
      "additionalProperties": false,
      "properties": {
        "uuid": {
          "type": "string",
          "description": "Readable id for the people maintaining the preset. Ignored when seeding: every block gets a fresh id."
        },
        "bundle": {
          "type": "string",
          "minLength": 1,
          "description": "Block type, as the theme defines it, e.g. 'hero'."
        },
        "props": {
          "type": "object",
          "description": "Content values of the block in the source language.",
          "additionalProperties": true
        },
        "props_i18n": {
          "type": "object",
          "description": "Content values per further language code, overriding `props`.",
          "additionalProperties": true
        },
        "options": {
          "type": "object",
          "description": "Display options of the block, e.g. size or colour.",
          "additionalProperties": true
        },
        "fragment_name": {
          "type": ["string", "null"],
          "description": "Name of the code fragment a fragment block renders."
        },
        "children": {
          "type": "object",
          "description": "Nested blocks, per field of this block.",
          "propertyNames": { "description": "Field name of the parent block, e.g. 'content'." },
          "additionalProperties": {
            "type": "array",
            "items": { "$ref": "#/$defs/block" }
          }
        }
      }
    },
    "menu": {
      "type": "object",
      "description": "One navigation menu.",
      "required": ["id", "label"],
      "additionalProperties": false,
      "properties": {
        "id": {
          "type": "string",
          "minLength": 1,
          "description": "Menu key the theme renders the menu by, e.g. 'main' or 'footer'. The key a menu is matched on."
        },
        "label": {
          "type": "string",
          "description": "Menu name shown to editors."
        },
        "items": {
          "type": "array",
          "description": "Menu entries, in order.",
          "items": { "$ref": "#/$defs/menuItem" }
        }
      }
    },
    "menuItem": {
      "type": "object",
      "description": "One menu entry.",
      "required": ["label"],
      "additionalProperties": true,
      "properties": {
        "label": { "type": "string", "description": "Entry text, or a translation key the theme resolves." },
        "to": { "type": "string", "description": "Link target, e.g. '/contact'." },
        "children": {
          "type": "array",
          "description": "Nested entries.",
          "items": { "$ref": "#/$defs/menuItem" }
        }
      }
    },
    "libraryItem": {
      "type": "object",
      "description": "One reusable block for the tenant's block library.",
      "required": ["label", "bundle", "tree"],
      "additionalProperties": false,
      "properties": {
        "label": {
          "type": "string",
          "minLength": 1,
          "description": "Name shown in the block library. The key a library item is matched on."
        },
        "bundle": {
          "type": "string",
          "minLength": 1,
          "description": "Block type of the reusable block."
        },
        "tree": {
          "type": "array",
          "description": "Content of the reusable block.",
          "items": { "$ref": "#/$defs/block" }
        }
      }
    },
    "design": {
      "type": "object",
      "description": "The tenant's brand. The theme derives a full colour ramp from `primary`, with the brand colour exactly at step 500, and applies radius and font site-wide.",
      "additionalProperties": false,
      "properties": {
        "primary": {
          "type": "string",
          "pattern": "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$",
          "description": "Brand colour as hex, e.g. '#00875a'."
        },
        "neutral": {
          "type": "string",
          "pattern": "^[a-z]+$",
          "description": "Name of the neutral grey palette, e.g. 'slate', 'gray', 'zinc', 'neutral' or 'stone'."
        },
        "radius": {
          "type": "string",
          "pattern": "^(?:0|\\d*\\.?\\d+(?:rem|px|em))$",
          "description": "Base corner radius as a CSS length, e.g. '0.75rem'."
        },
        "font": {
          "type": "object",
          "description": "Web font for the site.",
          "required": ["family"],
          "additionalProperties": false,
          "properties": {
            "family": { "type": "string", "minLength": 1, "description": "Font family name, e.g. 'Barlow'." },
            "href": {
              "type": "string",
              "format": "uri",
              "pattern": "^https://(?:fonts\\.bunny\\.net|fonts\\.googleapis\\.com|cdn\\.revenexx\\.com)/",
              "description": "Stylesheet that loads the font, from an allowed host: fonts.bunny.net, fonts.googleapis.com or cdn.revenexx.com."
            }
          }
        }
      }
    },
    "appearance": {
      "type": "object",
      "description": "Appearance presets of the theme: colour scheme, corner style, spacing density and font family. The values are the ones the theme offers in its appearance settings.",
      "additionalProperties": true,
      "properties": {
        "colorScheme": { "type": "string", "description": "Colour scheme id the theme offers." },
        "cornerStyle": { "type": "string", "description": "Corner style id the theme offers, e.g. 'rounded'." },
        "spacingDensity": { "type": "string", "description": "Spacing density id the theme offers, e.g. 'comfortable'." },
        "fontFamily": { "type": "string", "description": "Font family id the theme offers, e.g. 'default'." }
      }
    }
  }
}
