{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://schemas.rasd.dev/theme/v1.json",
  "title": "Rasd Theme v1",
  "description": "Design-token theme for Rasd Forms. Numbers are CSS px on web and dp on native (font sizes are emitted as rem on web); durations are milliseconds; colors are hex/rgb()/hsl() strings. Any token leaf may instead be a DTCG-style alias string such as \"{color.primary}\". Unknown properties are ignored-and-preserved within a MAJOR (rasd theme check warns). Property `ext` is host payload and is never interpreted.",
  "$comment": "Normative source: docs/12-theming.md and docs/00-decisions-and-conventions.md §10. This root schema describes both a full theme (what @rasd/themes exports, what createTheme() returns and what <RasdProvider theme> accepts) and the `partial` argument of createTheme(partial, { extends }) — `id` is required, everything else optional unless `extends` is null (root theme => CompleteTokens). The `overrides` object of an RFD `settings.theme` validates against #/$defs/ThemePartial (no identity keys). Built-in defaults per token are tabulated in docs/12-theming.md §1.2.",
  "type": "object",
  "required": [
    "id"
  ],
  "properties": {
    "$schema": {
      "type": "string",
      "format": "uri"
    },
    "rasdTheme": {
      "type": "string",
      "pattern": "^1\\.[0-9]+$",
      "default": "1.0",
      "description": "Theme spec version MAJOR.MINOR (mirrors RFD `rasd`). Consumers ignore-and-preserve unknown properties within a MAJOR; MAJOR bumps ship migrateTheme()."
    },
    "id": {
      "type": "string",
      "pattern": "^[a-z0-9][a-z0-9-]{1,63}$",
      "description": "Stable theme slug, unique per host. Built-ins: rasd-light, rasd-dark, rasd-high-contrast, rasd-field."
    },
    "name": {
      "type": "string",
      "maxLength": 120
    },
    "description": {
      "type": "string"
    },
    "version": {
      "type": "string",
      "pattern": "^\\d+\\.\\d+\\.\\d+(-[0-9A-Za-z.-]+)?$",
      "description": "Semver of this theme document (cache-busting, tenant rollout). Not the spec version - that is `rasdTheme`."
    },
    "extends": {
      "oneOf": [
        {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{1,63}$"
        },
        {
          "type": "null"
        }
      ],
      "description": "Parent theme id. Omitted => implicit `rasd-light`. `null` => root theme, which must carry a complete token set."
    },
    "mode": {
      "type": "string",
      "enum": [
        "light",
        "dark"
      ],
      "default": "light",
      "description": "Color scheme the base `tokens` are designed for. `modes.dark`/`modes.light` supply the opposite scheme; the overlay whose name equals `mode` is never applied."
    },
    "direction": {
      "type": "string",
      "enum": [
        "auto",
        "ltr",
        "rtl"
      ],
      "default": "auto",
      "description": "Forces layout direction; `auto` derives it from the form locale."
    },
    "density": {
      "type": "string",
      "enum": [
        "compact",
        "comfortable",
        "spacious"
      ],
      "default": "comfortable",
      "description": "Default density when the host does not force one. Base tokens describe `comfortable`; `modes.compact`/`modes.spacious` supply the others."
    },
    "tokens": {
      "$ref": "#/$defs/Tokens"
    },
    "modes": {
      "$ref": "#/$defs/Modes"
    },
    "components": {
      "$ref": "#/$defs/Components"
    },
    "assets": {
      "$ref": "#/$defs/Assets"
    },
    "ext": {
      "$ref": "#/$defs/Ext"
    }
  },
  "if": {
    "properties": {
      "extends": {
        "type": "null"
      }
    },
    "required": [
      "extends"
    ]
  },
  "then": {
    "required": [
      "tokens"
    ],
    "properties": {
      "tokens": {
        "$ref": "#/$defs/CompleteTokens"
      }
    }
  },
  "$defs": {
    "Alias": {
      "type": "string",
      "pattern": "^\\{[a-zA-Z][a-zA-Z0-9]*(\\.[a-zA-Z0-9]+)+\\}$",
      "description": "DTCG-style reference to another token, e.g. \"{color.primary}\". Resolved after merge; max depth 4."
    },
    "Color": {
      "oneOf": [
        {
          "type": "string",
          "pattern": "^(#([0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})|(rgb|rgba|hsl|hsla)\\([^)]*\\)|transparent)$",
          "description": "Hex, rgb()/rgba(), hsl()/hsla() or `transparent`. No oklch/lab (React Native cannot parse them)."
        },
        {
          "$ref": "#/$defs/Alias"
        }
      ]
    },
    "Px": {
      "oneOf": [
        {
          "type": "number",
          "minimum": 0,
          "maximum": 4096
        },
        {
          "$ref": "#/$defs/Alias"
        }
      ],
      "description": "Length in CSS px (web) / dp (native)."
    },
    "Ms": {
      "oneOf": [
        {
          "type": "number",
          "minimum": 0,
          "maximum": 5000
        },
        {
          "$ref": "#/$defs/Alias"
        }
      ]
    },
    "Ratio": {
      "oneOf": [
        {
          "type": "number",
          "exclusiveMinimum": 0,
          "maximum": 4
        },
        {
          "$ref": "#/$defs/Alias"
        }
      ]
    },
    "FontFamily": {
      "oneOf": [
        {
          "type": "string",
          "minLength": 1,
          "maxLength": 300,
          "description": "CSS font-family list on web; the first family name is used on native."
        },
        {
          "$ref": "#/$defs/Alias"
        }
      ]
    },
    "FontWeight": {
      "oneOf": [
        {
          "type": "integer",
          "minimum": 100,
          "maximum": 900,
          "multipleOf": 100
        },
        {
          "$ref": "#/$defs/Alias"
        }
      ]
    },
    "RadiusName": {
      "type": "string",
      "enum": [
        "sm",
        "md",
        "lg",
        "full",
        "none"
      ]
    },
    "WeightName": {
      "type": "string",
      "enum": [
        "regular",
        "medium",
        "bold"
      ]
    },
    "Elevation": {
      "oneOf": [
        {
          "type": "object",
          "properties": {
            "x": {
              "type": "number",
              "default": 0
            },
            "y": {
              "type": "number"
            },
            "blur": {
              "type": "number",
              "minimum": 0
            },
            "spread": {
              "type": "number",
              "default": 0
            },
            "color": {
              "$ref": "#/$defs/Color"
            },
            "elevation": {
              "type": "integer",
              "minimum": 0,
              "maximum": 24,
              "description": "Android elevation fallback used when the native boxShadow style is unavailable (Rasd requires RN >= 0.81 New Architecture, where boxShadow is primary)."
            }
          },
          "required": [
            "y",
            "blur",
            "color"
          ],
          "additionalProperties": false
        },
        {
          "type": "string",
          "description": "Raw CSS box-shadow value. Web only - native falls back to no shadow; `rasd theme check` warns."
        },
        {
          "$ref": "#/$defs/Alias"
        }
      ]
    },
    "ColorTokens": {
      "type": "object",
      "properties": {
        "primary": {
          "$ref": "#/$defs/Color"
        },
        "onPrimary": {
          "$ref": "#/$defs/Color"
        },
        "surface": {
          "$ref": "#/$defs/Color"
        },
        "onSurface": {
          "$ref": "#/$defs/Color"
        },
        "surfaceVariant": {
          "$ref": "#/$defs/Color"
        },
        "onSurfaceVariant": {
          "$ref": "#/$defs/Color"
        },
        "outline": {
          "$ref": "#/$defs/Color"
        },
        "error": {
          "$ref": "#/$defs/Color"
        },
        "success": {
          "$ref": "#/$defs/Color"
        },
        "warning": {
          "$ref": "#/$defs/Color"
        },
        "info": {
          "$ref": "#/$defs/Color"
        },
        "focus": {
          "$ref": "#/$defs/Color"
        },
        "scrim": {
          "$ref": "#/$defs/Color"
        }
      }
    },
    "TypographyTokens": {
      "type": "object",
      "properties": {
        "fontFamily": {
          "$ref": "#/$defs/FontFamily"
        },
        "fontFamilyRtl": {
          "$ref": "#/$defs/FontFamily"
        },
        "fontFamilyMono": {
          "$ref": "#/$defs/FontFamily"
        },
        "baseSize": {
          "$ref": "#/$defs/Px"
        },
        "scale": {
          "$ref": "#/$defs/Ratio"
        },
        "lineHeight": {
          "$ref": "#/$defs/Ratio"
        },
        "lineHeightRtl": {
          "$ref": "#/$defs/Ratio"
        },
        "weights": {
          "type": "object",
          "properties": {
            "regular": {
              "$ref": "#/$defs/FontWeight"
            },
            "medium": {
              "$ref": "#/$defs/FontWeight"
            },
            "bold": {
              "$ref": "#/$defs/FontWeight"
            }
          }
        },
        "maxFontScale": {
          "oneOf": [
            {
              "type": "number",
              "minimum": 1,
              "maximum": 4
            },
            {
              "$ref": "#/$defs/Alias"
            }
          ],
          "description": "React Native maxFontSizeMultiplier. Values below 1.2 fail `rasd theme check`."
        }
      }
    },
    "SpacingTokens": {
      "type": "object",
      "properties": {
        "unit": {
          "$ref": "#/$defs/Px"
        },
        "stack": {
          "$ref": "#/$defs/Px",
          "description": "Vertical gap between elements."
        },
        "page": {
          "$ref": "#/$defs/Px",
          "description": "Page/inline padding of the form root."
        }
      }
    },
    "RadiusTokens": {
      "type": "object",
      "properties": {
        "sm": {
          "$ref": "#/$defs/Px"
        },
        "md": {
          "$ref": "#/$defs/Px"
        },
        "lg": {
          "$ref": "#/$defs/Px"
        },
        "full": {
          "$ref": "#/$defs/Px"
        }
      }
    },
    "ElevationTokens": {
      "type": "object",
      "properties": {
        "sm": {
          "$ref": "#/$defs/Elevation"
        },
        "md": {
          "$ref": "#/$defs/Elevation"
        },
        "lg": {
          "$ref": "#/$defs/Elevation"
        }
      }
    },
    "MotionTokens": {
      "type": "object",
      "properties": {
        "durationMs": {
          "$ref": "#/$defs/Ms"
        },
        "easing": {
          "oneOf": [
            {
              "type": "string",
              "pattern": "^(linear|ease|ease-in|ease-out|ease-in-out|cubic-bezier\\([^)]*\\))$"
            },
            {
              "$ref": "#/$defs/Alias"
            }
          ]
        },
        "reduced": {
          "type": "string",
          "enum": [
            "auto",
            "always",
            "never"
          ],
          "default": "auto",
          "description": "auto = follow OS reduced-motion; always = motion off; never = ignore OS (rasd theme check warns)."
        }
      }
    },
    "ControlTokens": {
      "type": "object",
      "properties": {
        "minTouch": {
          "$ref": "#/$defs/Px",
          "description": "Minimum hit target. < 24 is an error (WCAG 2.2 SC 2.5.8), < 44 a warning."
        },
        "height": {
          "$ref": "#/$defs/Px",
          "description": "Rendered as min-height so text scaling grows the control."
        },
        "borderWidth": {
          "$ref": "#/$defs/Px",
          "description": "< 1 is an error."
        },
        "focusRingWidth": {
          "$ref": "#/$defs/Px",
          "description": "< 2 is an error (SC 2.4.7 / 2.4.13 guidance)."
        },
        "focusRingOffset": {
          "$ref": "#/$defs/Px",
          "description": "Gap between control and ring. When < 2 the linter also checks focus against primary and outline."
        }
      }
    },
    "Tokens": {
      "type": "object",
      "description": "Partial token set (all groups and keys optional). Use CompleteTokens for root themes.",
      "properties": {
        "color": {
          "$ref": "#/$defs/ColorTokens"
        },
        "typography": {
          "$ref": "#/$defs/TypographyTokens"
        },
        "spacing": {
          "$ref": "#/$defs/SpacingTokens"
        },
        "radius": {
          "$ref": "#/$defs/RadiusTokens"
        },
        "elevation": {
          "$ref": "#/$defs/ElevationTokens"
        },
        "motion": {
          "$ref": "#/$defs/MotionTokens"
        },
        "control": {
          "$ref": "#/$defs/ControlTokens"
        }
      }
    },
    "CompleteTokens": {
      "allOf": [
        {
          "$ref": "#/$defs/Tokens"
        },
        {
          "required": [
            "color",
            "typography",
            "spacing",
            "radius",
            "elevation",
            "motion",
            "control"
          ],
          "properties": {
            "color": {
              "required": [
                "primary",
                "onPrimary",
                "surface",
                "onSurface",
                "surfaceVariant",
                "onSurfaceVariant",
                "outline",
                "error",
                "success",
                "warning",
                "info",
                "focus",
                "scrim"
              ],
              "type": "object",
              "properties": {
                "primary": true,
                "onPrimary": true,
                "surface": true,
                "onSurface": true,
                "surfaceVariant": true,
                "onSurfaceVariant": true,
                "outline": true,
                "error": true,
                "success": true,
                "warning": true,
                "info": true,
                "focus": true,
                "scrim": true
              }
            },
            "typography": {
              "required": [
                "fontFamily",
                "fontFamilyRtl",
                "baseSize",
                "scale",
                "lineHeight",
                "lineHeightRtl",
                "weights",
                "maxFontScale"
              ],
              "properties": {
                "weights": {
                  "required": [
                    "regular",
                    "medium",
                    "bold"
                  ],
                  "type": "object",
                  "properties": {
                    "regular": true,
                    "medium": true,
                    "bold": true
                  }
                },
                "fontFamily": true,
                "fontFamilyRtl": true,
                "baseSize": true,
                "scale": true,
                "lineHeight": true,
                "lineHeightRtl": true,
                "maxFontScale": true
              },
              "type": "object"
            },
            "spacing": {
              "required": [
                "unit",
                "stack",
                "page"
              ],
              "type": "object",
              "properties": {
                "unit": true,
                "stack": true,
                "page": true
              }
            },
            "radius": {
              "required": [
                "sm",
                "md",
                "lg",
                "full"
              ],
              "type": "object",
              "properties": {
                "sm": true,
                "md": true,
                "lg": true,
                "full": true
              }
            },
            "elevation": {
              "required": [
                "sm",
                "md",
                "lg"
              ],
              "type": "object",
              "properties": {
                "sm": true,
                "md": true,
                "lg": true
              }
            },
            "motion": {
              "required": [
                "durationMs",
                "easing",
                "reduced"
              ],
              "type": "object",
              "properties": {
                "durationMs": true,
                "easing": true,
                "reduced": true
              }
            },
            "control": {
              "required": [
                "minTouch",
                "height",
                "borderWidth",
                "focusRingWidth",
                "focusRingOffset"
              ],
              "type": "object",
              "properties": {
                "minTouch": true,
                "height": true,
                "borderWidth": true,
                "focusRingWidth": true,
                "focusRingOffset": true
              }
            }
          },
          "type": "object"
        }
      ]
    },
    "ComponentOverride": {
      "type": "object",
      "description": "Per-component token overrides read by the default components. Component names are the published component list in 12-theming.md §5.2 (e.g. Button, FieldWrapper, SelectOne, PageNav); accepted keys per component are documented in §1.4; unknown keys are ignored (rasd theme check warns). Element-level `appearance` in the form always wins over these; per-part classNames/styles win over both.",
      "properties": {
        "radius": {
          "oneOf": [
            {
              "$ref": "#/$defs/RadiusName"
            },
            {
              "$ref": "#/$defs/Px"
            }
          ]
        },
        "height": {
          "$ref": "#/$defs/Px"
        },
        "variant": {
          "type": "string",
          "maxLength": 40
        },
        "columns": {
          "type": "integer",
          "minimum": 1,
          "maximum": 6
        },
        "weight": {
          "$ref": "#/$defs/WeightName"
        },
        "labelWeight": {
          "$ref": "#/$defs/WeightName"
        },
        "requiredMarker": {
          "type": "string",
          "enum": [
            "asterisk",
            "text",
            "none"
          ]
        },
        "style": {
          "type": "string",
          "maxLength": 40
        },
        "position": {
          "type": "string",
          "maxLength": 40
        },
        "sticky": {
          "type": "boolean"
        },
        "icon": {
          "type": "string",
          "maxLength": 40
        },
        "showIndex": {
          "type": "boolean"
        },
        "ext": {
          "$ref": "#/$defs/Ext"
        }
      }
    },
    "Components": {
      "type": "object",
      "patternProperties": {
        "^[A-Za-z][A-Za-z0-9_-]{0,63}$": {
          "$ref": "#/$defs/ComponentOverride"
        }
      },
      "additionalProperties": false
    },
    "ModeOverlay": {
      "type": "object",
      "properties": {
        "tokens": {
          "$ref": "#/$defs/Tokens"
        },
        "components": {
          "$ref": "#/$defs/Components"
        }
      },
      "additionalProperties": false
    },
    "Modes": {
      "type": "object",
      "description": "Partial overlays applied in the fixed order colorScheme -> highContrast -> density -> reducedMotion, later overlays winning per key; aliases are resolved after all overlays. A key equal to the theme's own `mode` (light/dark) is ignored. Overlays are deep-merged across the `extends` chain.",
      "properties": {
        "light": {
          "$ref": "#/$defs/ModeOverlay"
        },
        "dark": {
          "$ref": "#/$defs/ModeOverlay"
        },
        "highContrast": {
          "$ref": "#/$defs/ModeOverlay"
        },
        "compact": {
          "$ref": "#/$defs/ModeOverlay"
        },
        "spacious": {
          "$ref": "#/$defs/ModeOverlay"
        },
        "reducedMotion": {
          "$ref": "#/$defs/ModeOverlay"
        }
      },
      "additionalProperties": false
    },
    "FontFace": {
      "type": "object",
      "properties": {
        "weight": {
          "$ref": "#/$defs/FontWeight"
        },
        "style": {
          "type": "string",
          "enum": [
            "normal",
            "italic"
          ],
          "default": "normal"
        },
        "src": {
          "type": "object",
          "properties": {
            "web": {
              "type": "string",
              "description": "Self-hosted .woff2 URL or path (relative to the host's asset base). Never a third-party CDN."
            },
            "native": {
              "type": "string",
              "description": "Static .ttf/.otf asset name (expo-font config plugin or useFonts). Variable fonts are rejected by rasd theme check."
            }
          },
          "minProperties": 1,
          "additionalProperties": false
        },
        "unicodeRange": {
          "type": "string",
          "description": "CSS unicode-range for this face (web). Arabic subsets should include the bidi controls U+061C, U+200C-200F, U+2066-2069 (see docs/13-i18n-rtl-accessibility.md §9)."
        },
        "display": {
          "type": "string",
          "enum": [
            "swap",
            "block",
            "fallback",
            "optional"
          ],
          "default": "swap"
        }
      },
      "required": [
        "weight",
        "src"
      ],
      "additionalProperties": false
    },
    "Assets": {
      "type": "object",
      "properties": {
        "fonts": {
          "type": "object",
          "description": "Keyed by family name as used in typography.fontFamily*; each value lists the static faces to embed.",
          "patternProperties": {
            "^[^{}$.]{1,80}$": {
              "type": "array",
              "items": {
                "$ref": "#/$defs/FontFace"
              },
              "minItems": 1
            }
          },
          "additionalProperties": false
        },
        "logo": {
          "type": "object",
          "properties": {
            "light": {
              "type": "string"
            },
            "dark": {
              "type": "string"
            },
            "height": {
              "$ref": "#/$defs/Px"
            },
            "alt": {
              "type": "string",
              "maxLength": 200
            }
          },
          "required": [
            "light"
          ],
          "additionalProperties": false
        }
      },
      "additionalProperties": false
    },
    "Ext": {
      "type": "object",
      "description": "Vendor payload keyed by reverse-DNS or org slug. Never interpreted by Rasd; always round-tripped.",
      "patternProperties": {
        "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$": {}
      },
      "additionalProperties": false
    },
    "ThemePartial": {
      "type": "object",
      "description": "Overlay shape accepted by RFD settings.theme.overrides (docs/04 settings.theme, docs/12-theming.md §1.1 and §10). Identity keys (id, name, version, extends) are not allowed here because an overlay is merged onto the theme the host resolved and has no identity of its own. The `partial` argument of createTheme() is NOT this shape - it is the root schema (id required).",
      "properties": {
        "mode": {
          "type": "string",
          "enum": [
            "light",
            "dark"
          ]
        },
        "direction": {
          "type": "string",
          "enum": [
            "auto",
            "ltr",
            "rtl"
          ]
        },
        "density": {
          "type": "string",
          "enum": [
            "compact",
            "comfortable",
            "spacious"
          ]
        },
        "tokens": {
          "$ref": "#/$defs/Tokens"
        },
        "modes": {
          "$ref": "#/$defs/Modes"
        },
        "components": {
          "$ref": "#/$defs/Components"
        },
        "assets": {
          "$ref": "#/$defs/Assets"
        },
        "ext": {
          "$ref": "#/$defs/Ext"
        }
      },
      "additionalProperties": false
    }
  }
}
