{
  "$defs": {
    "ImageConfig": {
      "description": "Image optimization configuration for the project.",
      "properties": {
        "formats": {
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "lazyLoad": {
          "type": "boolean"
        },
        "optimize": {
          "type": "boolean"
        },
        "picture": {
          "description": "Wrap a multi-format image in a <picture> with one <source> per format (default true). With it off the <img> carries a srcset in the preferred format alone, which browsers that cannot decode that format will still select from.",
          "type": "boolean"
        },
        "quality": {
          "properties": {
            "avif": {
              "type": "number"
            },
            "jpeg": {
              "type": "number"
            },
            "png": {
              "type": "number"
            },
            "webp": {
              "type": "number"
            }
          },
          "type": "object"
        },
        "remoteDomains": {
          "description": "Hostnames whose remote (https) images are optimized through /cdn-cgi/image transform URLs (cloudflare service only), e.g. [\"drive.usercontent.google.com\"]. Remote sources from other hosts are left untouched.",
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "service": {
          "description": "How images are optimized: \"build\" runs Sharp at build time; \"cloudflare\" emits /cdn-cgi/image transform URLs served by Cloudflare Image Transformations (requires the feature to be enabled on the serving zone).",
          "enum": [
            "build",
            "cloudflare"
          ],
          "type": "string"
        },
        "sizes": {
          "type": "string"
        },
        "widths": {
          "items": {
            "type": "number"
          },
          "type": "array"
        }
      },
      "type": "object"
    },
    "StyleObject": {
      "additionalProperties": {
        "oneOf": [
          {
            "type": "string"
          },
          {
            "type": "number"
          },
          {
            "$ref": "#/$defs/StyleObject"
          }
        ]
      },
      "description": "CSS style definition. camelCase property names follow CSSOM convention. Keys starting with :, ., &, or [ are treated as nested CSS selectors. Keys matching $media breakpoint names (or other @-prefixed at-rules) group nested rules. Nesting is recursive: selector and at-rule groups may nest to arbitrary depth (e.g. breakpoint → selector → pseudo-class), mirroring the compiler's recursive emission.",
      "properties": {},
      "type": "object"
    }
  },
  "$id": "https://jxsuite.com/schema/project/v1",
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "description": "Schema for Jx project.json files. A project.json file is the root anchor file for a Jx project, declaring site metadata, default settings, global styles, extensions, and build configuration. Open by design: extension-contributed sections are opaque top-level keys; the generated per-project entry document (project.schema.json) closes the composition.",
  "properties": {
    "$defs": {
      "description": "Global type definitions available to all pages.",
      "type": "object"
    },
    "$elements": {
      "description": "Global custom element dependencies available to all pages. Items are $ref objects or npm package specifier strings.",
      "items": {
        "oneOf": [
          {
            "additionalProperties": false,
            "properties": {
              "$ref": {
                "type": "string"
              }
            },
            "required": [
              "$ref"
            ],
            "type": "object"
          },
          {
            "type": "string"
          }
        ]
      },
      "type": "array"
    },
    "$head": {
      "description": "Global <head> entries applied to all pages. Array of element definitions for meta tags, link tags, script tags, etc.",
      "examples": [
        [
          {
            "attributes": {
              "href": "/favicon.svg",
              "rel": "icon"
            },
            "tagName": "link"
          },
          {
            "attributes": {
              "content": "Jx",
              "name": "generator"
            },
            "tagName": "meta"
          }
        ]
      ],
      "items": {
        "properties": {
          "attributes": {
            "additionalProperties": {
              "type": "string"
            },
            "type": "object"
          },
          "tagName": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "type": "array"
    },
    "$media": {
      "additionalProperties": {
        "type": "string"
      },
      "description": "Named media breakpoints following CSS @custom-media convention. Available in all component style objects. Declaring a pure color-scheme query (e.g. \"--dark\": \"(prefers-color-scheme: dark)\") additionally opts the site into the forced-scheme contract: @--dark style blocks are dual-emitted so a data-color-scheme attribute on <html> overrides the OS preference, color-scheme is declared on :root, and a pre-paint script restores the visitor's persisted choice (spec §9.5).",
      "examples": [
        {
          "--dark": "(prefers-color-scheme: dark)",
          "--lg": "(min-width: 1024px)",
          "--md": "(min-width: 768px)",
          "--sm": "(min-width: 640px)"
        }
      ],
      "type": "object"
    },
    "$schema": {
      "description": "Relative path to the generated per-project schema (conventionally ./project.schema.json, written by `jx schema`).",
      "type": "string"
    },
    "build": {
      "description": "Build configuration.",
      "properties": {
        "adapter": {
          "description": "Platform adapter for deployment-specific output.",
          "enum": [
            "static",
            "cloudflare-pages",
            "cloudflare-workers",
            "node",
            "bun"
          ],
          "type": "string"
        },
        "deploy": {
          "additionalProperties": false,
          "description": "Deployment tracking: the hosting project this repo publishes to. Identifiers only (no secrets) — safe to commit; Studio uses it to tell whether the publish workflow already exists. `adapter` says how the build is packaged; `deploy` says where it ships.",
          "properties": {
            "accountId": {
              "description": "Hosting account id (e.g. the Cloudflare account id).",
              "type": "string"
            },
            "productionUrl": {
              "description": "Production URL of the connected hosting project.",
              "type": "string"
            },
            "projectName": {
              "description": "Hosting project name (e.g. the Cloudflare Pages project).",
              "pattern": "^[a-z0-9][a-z0-9-]*$",
              "type": "string"
            },
            "provider": {
              "description": "Hosting provider the project is connected to.",
              "enum": [
                "cloudflare-pages"
              ],
              "type": "string"
            }
          },
          "required": [
            "provider",
            "accountId",
            "projectName"
          ],
          "type": "object"
        },
        "format": {
          "default": "directory",
          "description": "Reserved; currently unused. Accepted for forward compatibility with single-file output (desktop single-file mode) — the build emits per-route directories regardless.",
          "enum": [
            "directory",
            "single"
          ],
          "type": "string"
        },
        "outDir": {
          "default": "./dist",
          "description": "Output directory for compiled site.",
          "type": "string"
        },
        "headers": {
          "additionalProperties": false,
          "description": "Emit dist/_headers — the response headers only the build can know. Honored by Cloudflare Pages/Workers assets and Netlify; on the node/bun adapters the file documents what a reverse proxy in front of them must send.",
          "properties": {
            "cache": {
              "default": "auto",
              "description": "\"auto\" marks the content-addressed image variants immutable (RFC 8246) and revalidates everything else (RFC 9111). \"off\" emits no Cache-Control at all.",
              "enum": [
                "auto",
                "off"
              ],
              "type": "string"
            },
            "enabled": {
              "default": true,
              "type": "boolean"
            },
            "rules": {
              "additionalProperties": {
                "additionalProperties": {
                  "type": "string"
                },
                "type": "object"
              },
              "description": "Verbatim rules, merged after the generated block.",
              "propertyNames": {
                "pattern": "^/"
              },
              "type": "object"
            },
            "security": {
              "additionalProperties": false,
              "properties": {
                "contentTypeOptions": {
                  "default": true,
                  "type": "boolean"
                },
                "csp": {
                  "default": false,
                  "description": "CSP Level 3. Off by default: unlike the other security headers this one governs code the build cannot see — a third-party script that loads a second script, a widget that opens a frame — so turning it on is a change to verify in the browser console, not a safe default. `script-src` is derived from the inline scripts and external origins the build emitted; `style-src` keeps 'unsafe-inline', because per-page <style> blocks and per-element style attributes have no site-wide hash.",
                  "oneOf": [
                    {
                      "type": "boolean"
                    },
                    {
                      "const": "report-only"
                    },
                    {
                      "additionalProperties": false,
                      "properties": {
                        "directives": {
                          "additionalProperties": {
                            "oneOf": [
                              {
                                "type": "string"
                              },
                              {
                                "const": false
                              }
                            ]
                          },
                          "description": "Replace (string) or remove (false) a computed directive. A replacement is wholesale, so an addition restates the defaults.",
                          "type": "object"
                        },
                        "mode": {
                          "default": "enforce",
                          "enum": [
                            "enforce",
                            "report-only"
                          ]
                        },
                        "reportUri": {
                          "format": "uri-reference",
                          "type": "string"
                        }
                      },
                      "type": "object"
                    }
                  ]
                },
                "frameOptions": {
                  "default": "SAMEORIGIN",
                  "oneOf": [
                    {
                      "enum": [
                        "DENY",
                        "SAMEORIGIN"
                      ],
                      "type": "string"
                    },
                    {
                      "const": false
                    }
                  ]
                },
                "hsts": {
                  "default": false,
                  "description": "RFC 6797. Off by default: a wrong max-age locks the apex domain to HTTPS for that long, and the mistake is invisible until a certificate lapses. `preload` requires `includeSubDomains` and is a build error without it.",
                  "oneOf": [
                    {
                      "type": "boolean"
                    },
                    {
                      "additionalProperties": false,
                      "properties": {
                        "includeSubDomains": {
                          "default": true,
                          "type": "boolean"
                        },
                        "maxAge": {
                          "default": 31536000,
                          "minimum": 0,
                          "type": "integer"
                        },
                        "preload": {
                          "default": false,
                          "type": "boolean"
                        }
                      },
                      "type": "object"
                    }
                  ]
                },
                "permissionsPolicy": {
                  "default": "camera=(), microphone=(), geolocation=()",
                  "oneOf": [
                    {
                      "type": "string"
                    },
                    {
                      "const": false
                    }
                  ]
                },
                "referrerPolicy": {
                  "default": "strict-origin-when-cross-origin",
                  "oneOf": [
                    {
                      "enum": [
                        "no-referrer",
                        "no-referrer-when-downgrade",
                        "origin",
                        "origin-when-cross-origin",
                        "same-origin",
                        "strict-origin",
                        "strict-origin-when-cross-origin",
                        "unsafe-url"
                      ],
                      "type": "string"
                    },
                    {
                      "const": false
                    }
                  ]
                }
              },
              "type": "object"
            }
          },
          "type": "object"
        },
        "sitemap": {
          "default": true,
          "description": "Generate sitemap.xml from the route table (requires `url`). Set false to disable.",
          "type": "boolean"
        },
        "trailingSlash": {
          "default": "always",
          "description": "Trailing slash behavior for generated URLs.",
          "enum": [
            "always",
            "never",
            "ignore"
          ],
          "type": "string"
        }
      },
      "type": "object"
    },
    "copy": {
      "additionalProperties": {
        "type": "string"
      },
      "description": "Declarative file copy map. Keys are source paths (relative to project root), values are destination paths (relative to outDir).",
      "type": "object"
    },
    "defaults": {
      "description": "Default settings applied to all pages unless overridden.",
      "properties": {
        "charset": {
          "default": "utf8",
          "description": "Default charset for the page.",
          "type": "string"
        },
        "dir": {
          "description": "Base direction for the <html> element. Omitted when unset — a page's `$dir` overrides it. Right-to-left content renders left-to-right without this.",
          "enum": [
            "ltr",
            "rtl",
            "auto"
          ],
          "type": "string"
        },
        "lang": {
          "default": "en",
          "description": "Default lang attribute for the <html> element. A page's `$lang` overrides it.",
          "type": "string"
        },
        "layout": {
          "default": null,
          "description": "Default layout file path applied to all pages. Set to null to render pages without a layout.",
          "examples": [
            "./layouts/base.json"
          ],
          "oneOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ]
        },
        "shadow": {
          "default": false,
          "description": "Default shadow-DOM mode for every component (spec.md §16.6). Off unless set: a shadow root isolates component styles from the page and replaces the <slot> emulation with real slot distribution. A component's own `$shadow` overrides this in both directions.",
          "oneOf": [
            {
              "enum": [
                "open",
                "closed"
              ],
              "type": "string"
            },
            {
              "const": false,
              "type": "boolean"
            }
          ]
        }
      },
      "type": "object"
    },
    "extensions": {
      "description": "Extension packages: bare package names (resolved project-first through the package exports map) or relative paths. Each must export jx-extension.json.",
      "examples": [
        [
          "@jxsuite/parser"
        ]
      ],
      "items": {
        "type": "string"
      },
      "type": "array"
    },
    "manifest": {
      "additionalProperties": false,
      "description": "W3C Web App Manifest, emitted as manifest.webmanifest with a <link rel=\"manifest\"> on every page. Absent means no manifest: it is a claim that a site is meant to be installed, and most are not.",
      "properties": {
        "backgroundColor": {
          "type": "string"
        },
        "categories": {
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "description": {
          "type": "string"
        },
        "dir": {
          "enum": [
            "ltr",
            "rtl",
            "auto"
          ],
          "type": "string"
        },
        "display": {
          "default": "standalone",
          "enum": [
            "fullscreen",
            "standalone",
            "minimal-ui",
            "browser"
          ],
          "type": "string"
        },
        "enabled": {
          "default": true,
          "type": "boolean"
        },
        "icons": {
          "description": "Icons at 192px and 512px are what browsers require before offering to install the site; the build warns when either is missing.",
          "items": {
            "additionalProperties": false,
            "properties": {
              "purpose": {
                "type": "string"
              },
              "sizes": {
                "examples": [
                  "192x192"
                ],
                "type": "string"
              },
              "src": {
                "type": "string"
              },
              "type": {
                "type": "string"
              }
            },
            "required": [
              "src"
            ],
            "type": "object"
          },
          "type": "array"
        },
        "lang": {
          "type": "string"
        },
        "name": {
          "description": "Defaults to the project name.",
          "type": "string"
        },
        "orientation": {
          "type": "string"
        },
        "scope": {
          "type": "string"
        },
        "shortName": {
          "type": "string"
        },
        "startUrl": {
          "default": "/",
          "type": "string"
        },
        "themeColor": {
          "type": "string"
        }
      },
      "type": "object"
    },
    "serviceWorker": {
      "default": false,
      "description": "W3C Service Worker, emitted as sw.js. Off unless declared — a worker is sticky, so it is the one output that can keep breaking visitors after a deploy tries to fix them. **Turning it off means `false`, not deleting the key**: `false` emits a tombstone at the same URL that unregisters the old worker and clears its caches, and a 404 there is not an instruction to stop. HTML is always network-first; only the content-addressed /images/_optimized/* is served cache-first.",
      "oneOf": [
        {
          "type": "boolean"
        },
        {
          "additionalProperties": false,
          "properties": {
            "enabled": {
              "default": true,
              "type": "boolean"
            },
            "offlineFallback": {
              "description": "Page served for a failed navigation. Added to `precache` if not already there.",
              "type": "string"
            },
            "precache": {
              "description": "Site-absolute URLs fetched at install time. Each must be a file this build produces; one that is not fails the build, because a single unreachable entry stops the worker installing at all.",
              "items": {
                "pattern": "^/",
                "type": "string"
              },
              "type": "array"
            },
            "scope": {
              "default": "/",
              "type": "string"
            }
          },
          "type": "object"
        }
      ]
    },
    "securityTxt": {
      "additionalProperties": false,
      "description": "RFC 9116 security.txt, emitted at .well-known/security.txt. `expires` is required by §2.5.5 and an absent or past value is a build error — an expired file advertises a reporting channel while telling the reporter not to trust it. A hand-placed public/.well-known/security.txt shadows this, which is how a clearsigned file ships.",
      "properties": {
        "acknowledgments": {
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "canonical": {
          "type": "string"
        },
        "contact": {
          "description": "At least one mailto:, https: or tel: URI (§2.5.3).",
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "enabled": {
          "default": true,
          "type": "boolean"
        },
        "encryption": {
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "expires": {
          "format": "date-time",
          "type": "string"
        },
        "hiring": {
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "policy": {
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "preferredLanguages": {
          "description": "BCP 47 tags, validated and canonicalized like i18n.locales.",
          "items": {
            "pattern": "^[A-Za-z]{2,8}(-[A-Za-z0-9]{1,8})*$",
            "type": "string"
          },
          "type": "array"
        }
      },
      "type": "object"
    },
    "i18n": {
      "description": "Internationalization configuration.",
      "properties": {
        "defaultLocale": {
          "description": "Default locale, as a BCP 47 language tag. The pattern catches shape errors (`en_US`, `en--US`); the build is the authority on well-formedness.",
          "examples": [
            "en"
          ],
          "pattern": "^[A-Za-z]{2,8}(-[A-Za-z0-9]{1,8})*$",
          "type": "string"
        },
        "locales": {
          "description": "Available locales, as BCP 47 language tags.",
          "examples": [
            [
              "en",
              "fr",
              "de"
            ]
          ],
          "items": {
            "pattern": "^[A-Za-z]{2,8}(-[A-Za-z0-9]{1,8})*$",
            "type": "string"
          },
          "type": "array"
        },
        "routing": {
          "description": "Locale routing strategy.",
          "enum": [
            "prefix-except-default",
            "prefix-always"
          ],
          "type": "string"
        }
      },
      "type": "object"
    },
    "images": {
      "$ref": "#/$defs/ImageConfig"
    },
    "imports": {
      "additionalProperties": {
        "type": "string"
      },
      "description": "Global import map: $prototype names to .class.json file paths. Makes external classes available by name in all pages.",
      "examples": [
        {
          "Markdown": "@jxsuite/parser/Markdown.class.json",
          "MarkdownCollection": "@jxsuite/parser/MarkdownCollection.class.json"
        }
      ],
      "type": "object"
    },
    "name": {
      "default": "Jx Site",
      "description": "Human-readable project name.",
      "examples": [
        "My Portfolio",
        "Jx Example Site"
      ],
      "type": "string"
    },
    "redirects": {
      "additionalProperties": {
        "oneOf": [
          {
            "description": "Destination path or URL; implies a 301.",
            "type": "string"
          },
          {
            "additionalProperties": false,
            "description": "A redirect with an explicit RFC 9110 §15.4 status.",
            "properties": {
              "destination": {
                "type": "string"
              },
              "status": {
                "default": 301,
                "description": "RFC 9110 §15.4 redirection status. 307 and 308 preserve the request method; 301 and 302 historically do not.",
                "enum": [
                  301,
                  302,
                  303,
                  307,
                  308
                ],
                "type": "integer"
              }
            },
            "required": [
              "destination"
            ],
            "type": "object"
          },
          {
            "additionalProperties": false,
            "description": "A rewrite (proxy): the destination's content is served AT the source URL, with no redirect. Emitted as status 200 in `_redirects`, which is the host convention — not an HTTP redirection status.",
            "properties": {
              "destination": {
                "type": "string"
              },
              "rewrite": {
                "const": true,
                "type": "boolean"
              }
            },
            "required": [
              "destination",
              "rewrite"
            ],
            "type": "object"
          }
        ]
      },
      "description": "Static redirect and rewrite rules. Keys are source paths in URLPattern pathname syntax.",
      "examples": [
        {
          "/api/*": {
            "destination": "https://api.example.com/*",
            "rewrite": true
          },
          "/legacy/*": {
            "destination": "/archive/*",
            "status": 308
          },
          "/old-about": "/about"
        }
      ],
      "propertyNames": {
        "pattern": "^/"
      },
      "type": "object"
    },
    "state": {
      "description": "Site-wide reactive state available to all pages.",
      "type": "object"
    },
    "style": {
      "additionalProperties": {
        "oneOf": [
          {
            "type": "string"
          },
          {
            "type": "number"
          },
          {
            "$ref": "#/$defs/StyleObject"
          }
        ]
      },
      "description": "Global CSS styles applied to the <body> element. Uses the same camelCase CSSOM convention and recursive selector/at-rule nesting as component styles.",
      "type": "object"
    },
    "url": {
      "description": "Production URL of the deployed site.",
      "examples": [
        "https://example.com",
        "https://jxsuite.com"
      ],
      "type": "string"
    }
  },
  "title": "Jx Project",
  "type": "object"
}