@janga/norna 0.7.13 → 0.7.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/README.md +15 -9
  2. package/bin/norna-cli.mjs +11 -4
  3. package/docs/README.md +71 -43
  4. package/docs/commands.md +34 -20
  5. package/docs/configuration.md +43 -62
  6. package/docs/content.md +34 -7
  7. package/docs/design/command-organization.md +15 -8
  8. package/docs/design/norna-diagram-design.md +6 -6
  9. package/docs/design/site-examples-structure.md +1 -1
  10. package/docs/engine-development.md +22 -9
  11. package/docs/getting-started.md +29 -16
  12. package/docs/how-to/embedded-site.md +1 -1
  13. package/docs/images-and-metadata.md +3 -3
  14. package/docs/local-development.md +11 -3
  15. package/docs/public-files.md +105 -0
  16. package/docs/publishing.md +4 -8
  17. package/docs/routes.md +6 -7
  18. package/docs/site-files.md +144 -0
  19. package/docs/sitewide-content.md +47 -33
  20. package/docs/theme.md +37 -49
  21. package/docs/typography.md +25 -31
  22. package/examples/complete-sites/dog-shelter-multi-page/README.md +8 -0
  23. package/examples/complete-sites/{dog-shelter-single-page/site/config.md → dog-shelter-multi-page/site/config.yaml} +0 -2
  24. package/examples/complete-sites/dog-shelter-multi-page/site/sitewide-content.yaml +6 -0
  25. package/examples/complete-sites/dog-shelter-multi-page/site/{theme.md → theme.yaml} +0 -2
  26. package/examples/complete-sites/dog-shelter-single-page/README.md +8 -0
  27. package/examples/complete-sites/{dog-shelter-multi-page/site/config.md → dog-shelter-single-page/site/config.yaml} +0 -2
  28. package/examples/complete-sites/dog-shelter-single-page/site/sitewide-content.yaml +6 -0
  29. package/examples/complete-sites/dog-shelter-single-page/site/{theme.md → theme.yaml} +0 -2
  30. package/examples/feature-demos/media-and-surfaces/.astro/data-store.json +1 -1
  31. package/examples/feature-demos/media-and-surfaces/README.md +1 -1
  32. package/examples/feature-demos/media-and-surfaces/site/{config.md → config.yaml} +0 -2
  33. package/examples/feature-demos/media-and-surfaces/site/content.md +2 -2
  34. package/examples/feature-demos/media-and-surfaces/site/routes/020-surfaces/{route-content.md → content.md} +3 -7
  35. package/examples/feature-demos/media-and-surfaces/site/routes/020-surfaces/theme.yaml +3 -0
  36. package/examples/feature-demos/media-and-surfaces/site/{sitewide-content.md → sitewide-content.yaml} +1 -3
  37. package/examples/feature-demos/media-and-surfaces/site/{theme.md → theme.yaml} +0 -2
  38. package/examples/feature-demos/sitewide-content/README.md +8 -0
  39. package/examples/feature-demos/sitewide-content/site/{config.md → config.yaml} +0 -2
  40. package/examples/feature-demos/sitewide-content/site/routes/010-identity/{route-content.md → content.md} +3 -3
  41. package/examples/feature-demos/sitewide-content/site/routes/020-notices/{route-content.md → content.md} +1 -1
  42. package/examples/feature-demos/sitewide-content/site/routes/030-footer/{route-content.md → content.md} +1 -1
  43. package/examples/feature-demos/sitewide-content/site/{sitewide-content.md → sitewide-content.yaml} +1 -11
  44. package/examples/feature-demos/sitewide-content/site/{theme.md → theme.yaml} +0 -2
  45. package/examples/feature-demos/theme-presets/site/config.yaml +1 -0
  46. package/examples/feature-demos/theme-presets/site/routes/010-portfolio/{theme.md → theme.yaml} +0 -2
  47. package/examples/feature-demos/theme-presets/site/routes/020-documentation/{theme.md → theme.yaml} +0 -2
  48. package/examples/feature-demos/theme-presets/site/routes/030-project/theme.yaml +1 -0
  49. package/examples/feature-demos/theme-presets/site/routes/040-statement/{route-content.md → content.md} +1 -1
  50. package/examples/feature-demos/theme-presets/site/routes/040-statement/{theme.md → theme.yaml} +0 -2
  51. package/examples/feature-demos/theme-presets/site/{sitewide-content.md → sitewide-content.yaml} +1 -3
  52. package/examples/feature-demos/theme-presets/site/theme.yaml +1 -0
  53. package/fixtures/basic/site/{config.md → config.yaml} +0 -2
  54. package/fixtures/basic/site/content.md +3 -3
  55. package/fixtures/basic/site/{sitewide-content.md → sitewide-content.yaml} +0 -2
  56. package/fixtures/basic/site/{theme.md → theme.yaml} +3 -6
  57. package/fixtures/content-model-v2/site/{config.md → config.yaml} +0 -2
  58. package/fixtures/content-model-v2/site/theme.yaml +4 -0
  59. package/package.json +10 -3
  60. package/schemas/config.schema.json +77 -0
  61. package/schemas/content-frontmatter.schema.json +81 -0
  62. package/schemas/manifest.json +9 -0
  63. package/schemas/sitewide-content.schema.json +215 -0
  64. package/schemas/theme.schema.json +1208 -0
  65. package/scripts/check-config.mjs +18 -11
  66. package/scripts/export-theme-preset.mjs +1 -1
  67. package/scripts/generate-schemas.mjs +283 -0
  68. package/scripts/init-site.mjs +22 -27
  69. package/scripts/lib/documentation-links.mjs +8 -0
  70. package/scripts/lib/editor-language-service.mjs +609 -0
  71. package/scripts/lib/example-sites.mjs +2 -2
  72. package/scripts/lib/git-status.mjs +7 -0
  73. package/scripts/lib/logo-assets.mjs +15 -10
  74. package/scripts/lib/norna-markdown-blocks.mjs +90 -11
  75. package/scripts/lib/presentation.mjs +23 -12
  76. package/scripts/lib/project-config.mjs +17 -41
  77. package/scripts/lib/public-asset-conventions.mjs +112 -0
  78. package/scripts/lib/schema-definitions.mjs +213 -0
  79. package/scripts/lib/schema-value-definitions.mjs +86 -0
  80. package/scripts/lib/site-content.mjs +24 -18
  81. package/scripts/lib/site-paths.mjs +4 -8
  82. package/scripts/lib/sitewide-content.mjs +24 -0
  83. package/scripts/lib/theme-config.mjs +15 -35
  84. package/scripts/lib/theme-presets.mjs +76 -66
  85. package/scripts/lib/typography.mjs +15 -15
  86. package/scripts/lib/yaml-config.mjs +50 -0
  87. package/scripts/list-theme-presets.mjs +15 -0
  88. package/scripts/release.mjs +84 -8
  89. package/scripts/show-typography.mjs +22 -46
  90. package/scripts/sync-content-sections.mjs +2 -2
  91. package/scripts/test-client-javascript.mjs +28 -28
  92. package/scripts/test-content-check.mjs +21 -26
  93. package/scripts/test-content-model-v2.mjs +72 -40
  94. package/scripts/test-documentation.mjs +70 -4
  95. package/scripts/test-editor-language-service.mjs +158 -0
  96. package/scripts/test-engine-commands.mjs +65 -34
  97. package/scripts/test-package-check.mjs +37 -30
  98. package/scripts/test-project-config.mjs +15 -20
  99. package/scripts/test-schemas.mjs +102 -0
  100. package/scripts/test-temporary-visibility.mjs +5 -13
  101. package/scripts/test-theme-presets.mjs +64 -37
  102. package/src/components/CardList.astro +2 -5
  103. package/src/components/ImageCarousel.astro +21 -21
  104. package/src/components/{GalleryGrid.astro → ImageStack.astro} +12 -12
  105. package/src/components/SectionNavigationScript.astro +28 -0
  106. package/src/components/SiteNavigation.astro +6 -5
  107. package/src/components/SitePage.astro +8 -8
  108. package/src/components/SiteSection.astro +18 -21
  109. package/src/content.config.ts +17 -209
  110. package/src/layouts/BaseLayout.astro +9 -9
  111. package/src/lib/{galleryImages.ts → imagePresentation.ts} +7 -7
  112. package/src/lib/routeThemes.ts +9 -29
  113. package/src/lib/sectionContent.ts +3 -3
  114. package/src/lib/sitePublicAssets.ts +5 -21
  115. package/src/styles/global.css +73 -73
  116. package/starters/basic/.github/workflows/deploy.yml +1 -1
  117. package/starters/basic/README.md +16 -13
  118. package/starters/basic/package.json +3 -2
  119. package/starters/basic/site/{config.md → config.yaml} +0 -2
  120. package/starters/basic/site/content.md +1 -1
  121. package/starters/basic/site/{sitewide-content.md → sitewide-content.yaml} +1 -3
  122. package/starters/basic/site/{theme.md → theme.yaml} +8 -15
  123. package/starters/project/.github/workflows/deploy.yml +1 -1
  124. package/starters/project/README.md +22 -15
  125. package/starters/project/package.json +3 -2
  126. package/starters/project/site/{config.md → config.yaml} +0 -2
  127. package/starters/project/site/{sitewide-content.md → sitewide-content.yaml} +1 -3
  128. package/starters/project/site/{theme.md → theme.yaml} +4 -8
  129. package/docs/site-structure.md +0 -98
  130. package/examples/complete-sites/dog-shelter-multi-page/site/sitewide-content.md +0 -16
  131. package/examples/complete-sites/dog-shelter-single-page/site/sitewide-content.md +0 -16
  132. package/examples/feature-demos/media-and-surfaces/site/routes/020-surfaces/theme.md +0 -8
  133. package/examples/feature-demos/theme-presets/site/config.md +0 -3
  134. package/examples/feature-demos/theme-presets/site/routes/030-project/theme.md +0 -3
  135. package/examples/feature-demos/theme-presets/site/theme.md +0 -3
  136. package/fixtures/content-model-v2/site/theme.md +0 -7
  137. package/scripts/lib/sitewide-config.mjs +0 -34
  138. package/src/components/MobileSectionNavigationScript.astro +0 -14
  139. /package/examples/complete-sites/dog-shelter-multi-page/site/routes/010-dogs/{route-content.md → content.md} +0 -0
  140. /package/examples/complete-sites/dog-shelter-multi-page/site/routes/020-adopt/{route-content.md → content.md} +0 -0
  141. /package/examples/feature-demos/media-and-surfaces/site/routes/010-media/{route-content.md → content.md} +0 -0
  142. /package/examples/feature-demos/theme-presets/site/routes/010-portfolio/{route-content.md → content.md} +0 -0
  143. /package/examples/feature-demos/theme-presets/site/routes/020-documentation/{route-content.md → content.md} +0 -0
  144. /package/examples/feature-demos/theme-presets/site/routes/030-project/{route-content.md → content.md} +0 -0
  145. /package/fixtures/content-model-v2/site/routes/010-guide/{route-content.md → content.md} +0 -0
  146. /package/starters/project/site/routes/010-guide/{route-content.md → content.md} +0 -0
@@ -0,0 +1,1208 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "type": "object",
4
+ "properties": {
5
+ "preset": {
6
+ "description": "Complete visual starting point. Add only the overrides the site actually needs.",
7
+ "type": "string",
8
+ "oneOf": [
9
+ {
10
+ "const": "portfolio",
11
+ "title": "Portfolio",
12
+ "description": "For portfolios and image-led sites, with restrained typography and generous space for images."
13
+ },
14
+ {
15
+ "const": "documentation",
16
+ "title": "Documentation",
17
+ "description": "For guides and reference material, with reading-focused typography and compact spacing."
18
+ },
19
+ {
20
+ "const": "project",
21
+ "title": "Project",
22
+ "description": "For project and product sites that balance explanation, code, cards, and images."
23
+ },
24
+ {
25
+ "const": "statement",
26
+ "title": "Statement",
27
+ "description": "For short, expressive sites, with larger typography, airy spacing, and stronger section emphasis."
28
+ }
29
+ ],
30
+ "markdownDescription": "```yaml\npreset: documentation\n```\n\nSelects coordinated layout, image sizing, typography, palette and section surfaces. This is the normal starting point for a theme.\n\n[Compare theme presets](https://github.com/janga/norna/blob/v0.7.15/docs/theme.md#theme-presets)"
31
+ },
32
+ "layout": {
33
+ "type": "object",
34
+ "properties": {
35
+ "density": {
36
+ "description": "Overall spacing density. Omit this to keep the selected preset.",
37
+ "type": "string",
38
+ "oneOf": [
39
+ {
40
+ "const": "compact",
41
+ "title": "Compact",
42
+ "description": "Reduce vertical spacing while preserving readable separation."
43
+ },
44
+ {
45
+ "const": "normal",
46
+ "title": "Normal",
47
+ "description": "Use balanced vertical spacing."
48
+ },
49
+ {
50
+ "const": "airy",
51
+ "title": "Airy",
52
+ "description": "Increase vertical spacing for a more expansive presentation."
53
+ }
54
+ ]
55
+ },
56
+ "pageWidth": {
57
+ "description": "Maximum width of the site layout. Omit this to keep the selected preset.",
58
+ "type": "string",
59
+ "pattern": "^(?:0|(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)|clamp\\(\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*\\))$"
60
+ },
61
+ "gutter": {
62
+ "description": "Horizontal page gutter as one value or separate desktop and mobile values.",
63
+ "anyOf": [
64
+ {
65
+ "type": "string",
66
+ "pattern": "^(?:0|(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)|clamp\\(\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*\\))$",
67
+ "description": "Responsive CSS length or clamp() expression."
68
+ },
69
+ {
70
+ "type": "object",
71
+ "properties": {
72
+ "desktop": {
73
+ "type": "string",
74
+ "pattern": "^(?:0|(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)|clamp\\(\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*\\))$",
75
+ "description": "Responsive CSS length or clamp() expression."
76
+ },
77
+ "mobile": {
78
+ "type": "string",
79
+ "pattern": "^(?:0|(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)|clamp\\(\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*\\))$",
80
+ "description": "Responsive CSS length or clamp() expression."
81
+ }
82
+ },
83
+ "required": [
84
+ "desktop",
85
+ "mobile"
86
+ ],
87
+ "additionalProperties": false
88
+ }
89
+ ]
90
+ },
91
+ "spacing": {
92
+ "description": "Fine-grained spacing overrides.",
93
+ "type": "object",
94
+ "properties": {
95
+ "blockGap": {
96
+ "description": "Default vertical gap between structured content blocks.",
97
+ "anyOf": [
98
+ {
99
+ "type": "string",
100
+ "pattern": "^(?:0|(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)|clamp\\(\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*\\))$",
101
+ "description": "Responsive CSS length or clamp() expression."
102
+ },
103
+ {
104
+ "type": "object",
105
+ "properties": {
106
+ "desktop": {
107
+ "type": "string",
108
+ "pattern": "^(?:0|(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)|clamp\\(\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*\\))$",
109
+ "description": "Responsive CSS length or clamp() expression."
110
+ },
111
+ "mobile": {
112
+ "type": "string",
113
+ "pattern": "^(?:0|(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)|clamp\\(\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*\\))$",
114
+ "description": "Responsive CSS length or clamp() expression."
115
+ }
116
+ },
117
+ "required": [
118
+ "desktop",
119
+ "mobile"
120
+ ],
121
+ "additionalProperties": false
122
+ }
123
+ ]
124
+ },
125
+ "finalSectionBottom": {
126
+ "description": "Bottom space after the final section.",
127
+ "anyOf": [
128
+ {
129
+ "type": "string",
130
+ "pattern": "^(?:0|(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)|clamp\\(\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*\\))$",
131
+ "description": "Responsive CSS length or clamp() expression."
132
+ },
133
+ {
134
+ "type": "object",
135
+ "properties": {
136
+ "desktop": {
137
+ "type": "string",
138
+ "pattern": "^(?:0|(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)|clamp\\(\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*\\))$",
139
+ "description": "Responsive CSS length or clamp() expression."
140
+ },
141
+ "mobile": {
142
+ "type": "string",
143
+ "pattern": "^(?:0|(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)|clamp\\(\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*\\))$",
144
+ "description": "Responsive CSS length or clamp() expression."
145
+ }
146
+ },
147
+ "required": [
148
+ "desktop",
149
+ "mobile"
150
+ ],
151
+ "additionalProperties": false
152
+ }
153
+ ]
154
+ },
155
+ "firstSectionTop": {
156
+ "description": "Top space before the first section.",
157
+ "anyOf": [
158
+ {
159
+ "type": "string",
160
+ "pattern": "^(?:0|(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)|clamp\\(\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*\\))$",
161
+ "description": "Responsive CSS length or clamp() expression."
162
+ },
163
+ {
164
+ "type": "object",
165
+ "properties": {
166
+ "desktop": {
167
+ "type": "string",
168
+ "pattern": "^(?:0|(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)|clamp\\(\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*\\))$",
169
+ "description": "Responsive CSS length or clamp() expression."
170
+ },
171
+ "mobile": {
172
+ "type": "string",
173
+ "pattern": "^(?:0|(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)|clamp\\(\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*\\))$",
174
+ "description": "Responsive CSS length or clamp() expression."
175
+ }
176
+ },
177
+ "required": [
178
+ "desktop",
179
+ "mobile"
180
+ ],
181
+ "additionalProperties": false
182
+ }
183
+ ]
184
+ },
185
+ "headingToBlock": {
186
+ "description": "Gap from a section heading to a following structured block.",
187
+ "anyOf": [
188
+ {
189
+ "type": "string",
190
+ "pattern": "^(?:0|(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)|clamp\\(\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*\\))$",
191
+ "description": "Responsive CSS length or clamp() expression."
192
+ },
193
+ {
194
+ "type": "object",
195
+ "properties": {
196
+ "desktop": {
197
+ "type": "string",
198
+ "pattern": "^(?:0|(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)|clamp\\(\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*\\))$",
199
+ "description": "Responsive CSS length or clamp() expression."
200
+ },
201
+ "mobile": {
202
+ "type": "string",
203
+ "pattern": "^(?:0|(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)|clamp\\(\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*\\))$",
204
+ "description": "Responsive CSS length or clamp() expression."
205
+ }
206
+ },
207
+ "required": [
208
+ "desktop",
209
+ "mobile"
210
+ ],
211
+ "additionalProperties": false
212
+ }
213
+ ]
214
+ },
215
+ "imageGap": {
216
+ "description": "Gap between images in an image stack.",
217
+ "anyOf": [
218
+ {
219
+ "type": "string",
220
+ "pattern": "^(?:0|(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)|clamp\\(\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*\\))$",
221
+ "description": "Responsive CSS length or clamp() expression."
222
+ },
223
+ {
224
+ "type": "object",
225
+ "properties": {
226
+ "desktop": {
227
+ "type": "string",
228
+ "pattern": "^(?:0|(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)|clamp\\(\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*\\))$",
229
+ "description": "Responsive CSS length or clamp() expression."
230
+ },
231
+ "mobile": {
232
+ "type": "string",
233
+ "pattern": "^(?:0|(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)|clamp\\(\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*\\))$",
234
+ "description": "Responsive CSS length or clamp() expression."
235
+ }
236
+ },
237
+ "required": [
238
+ "desktop",
239
+ "mobile"
240
+ ],
241
+ "additionalProperties": false
242
+ }
243
+ ]
244
+ },
245
+ "sectionGap": {
246
+ "description": "Vertical separation between page sections.",
247
+ "anyOf": [
248
+ {
249
+ "type": "string",
250
+ "pattern": "^(?:0|(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)|clamp\\(\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*\\))$",
251
+ "description": "Responsive CSS length or clamp() expression."
252
+ },
253
+ {
254
+ "type": "object",
255
+ "properties": {
256
+ "desktop": {
257
+ "type": "string",
258
+ "pattern": "^(?:0|(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)|clamp\\(\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*\\))$",
259
+ "description": "Responsive CSS length or clamp() expression."
260
+ },
261
+ "mobile": {
262
+ "type": "string",
263
+ "pattern": "^(?:0|(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)|clamp\\(\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*\\))$",
264
+ "description": "Responsive CSS length or clamp() expression."
265
+ }
266
+ },
267
+ "required": [
268
+ "desktop",
269
+ "mobile"
270
+ ],
271
+ "additionalProperties": false
272
+ }
273
+ ]
274
+ }
275
+ },
276
+ "additionalProperties": false
277
+ }
278
+ },
279
+ "additionalProperties": false,
280
+ "description": "Optional layout overrides applied after the preset.",
281
+ "markdownDescription": "```yaml\nlayout:\n density: compact\n pageWidth: 72rem\n```\n\nOverrides page width, gutters and structural spacing after the selected preset.\n\n[Layout reference](https://github.com/janga/norna/blob/v0.7.15/docs/theme.md#layout)"
282
+ },
283
+ "images": {
284
+ "type": "object",
285
+ "properties": {
286
+ "width": {
287
+ "description": "Maximum managed-image width. Omit this to keep the selected preset.",
288
+ "type": "string",
289
+ "pattern": "^(?:0|(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)|clamp\\(\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*,\\s*(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)\\s*\\))$"
290
+ },
291
+ "maxAvailableWidthPercent": {
292
+ "description": "Maximum percentage of available horizontal space used by managed images.",
293
+ "anyOf": [
294
+ {
295
+ "type": "number",
296
+ "exclusiveMinimum": 0,
297
+ "maximum": 100
298
+ },
299
+ {
300
+ "type": "object",
301
+ "properties": {
302
+ "desktop": {
303
+ "type": "number",
304
+ "exclusiveMinimum": 0,
305
+ "maximum": 100,
306
+ "description": "Percentage used on wider screens."
307
+ },
308
+ "mobile": {
309
+ "type": "number",
310
+ "exclusiveMinimum": 0,
311
+ "maximum": 100,
312
+ "description": "Percentage used on narrow screens."
313
+ }
314
+ },
315
+ "required": [
316
+ "desktop",
317
+ "mobile"
318
+ ],
319
+ "additionalProperties": false
320
+ }
321
+ ]
322
+ },
323
+ "maxAvailableHeightPercent": {
324
+ "description": "Maximum percentage of viewport height used by managed images.",
325
+ "anyOf": [
326
+ {
327
+ "type": "number",
328
+ "exclusiveMinimum": 0,
329
+ "maximum": 100
330
+ },
331
+ {
332
+ "type": "object",
333
+ "properties": {
334
+ "desktop": {
335
+ "type": "number",
336
+ "exclusiveMinimum": 0,
337
+ "maximum": 100,
338
+ "description": "Percentage used on wider screens."
339
+ },
340
+ "mobile": {
341
+ "type": "number",
342
+ "exclusiveMinimum": 0,
343
+ "maximum": 100,
344
+ "description": "Percentage used on narrow screens."
345
+ }
346
+ },
347
+ "required": [
348
+ "desktop",
349
+ "mobile"
350
+ ],
351
+ "additionalProperties": false
352
+ }
353
+ ]
354
+ }
355
+ },
356
+ "additionalProperties": false,
357
+ "description": "Optional defaults for managed image presentation.",
358
+ "markdownDescription": "```yaml\nimages:\n width: 900px\n maxAvailableHeightPercent: 74\n```\n\nOverrides the available width and height used by Norna-managed images.\n\n[Image sizing reference](https://github.com/janga/norna/blob/v0.7.15/docs/theme.md#image-sizing)"
359
+ },
360
+ "typography": {
361
+ "type": "object",
362
+ "properties": {
363
+ "fontFamily": {
364
+ "description": "CSS font-family stack.",
365
+ "type": "string",
366
+ "minLength": 1
367
+ },
368
+ "profile": {
369
+ "description": "Typography profile. Omit this to keep the selected preset.",
370
+ "type": "string",
371
+ "oneOf": [
372
+ {
373
+ "const": "restrained",
374
+ "title": "Restrained",
375
+ "description": "Quiet typography with regular-weight headings and balanced line lengths."
376
+ },
377
+ {
378
+ "const": "dense",
379
+ "title": "Dense",
380
+ "description": "Compact typography with wider text and tighter line height."
381
+ },
382
+ {
383
+ "const": "reading",
384
+ "title": "Reading",
385
+ "description": "Reading-focused typography with narrower text and relaxed line height."
386
+ },
387
+ {
388
+ "const": "statement",
389
+ "title": "Statement",
390
+ "description": "Stronger headings and tighter body rhythm for short, expressive pages."
391
+ }
392
+ ]
393
+ },
394
+ "rhythm": {
395
+ "description": "Typography spacing rhythm. Omit this to keep the selected preset.",
396
+ "type": "string",
397
+ "oneOf": [
398
+ {
399
+ "const": "compact",
400
+ "title": "Compact",
401
+ "description": "Reduce vertical spacing while preserving readable separation."
402
+ },
403
+ {
404
+ "const": "normal",
405
+ "title": "Normal",
406
+ "description": "Use balanced vertical spacing."
407
+ },
408
+ {
409
+ "const": "airy",
410
+ "title": "Airy",
411
+ "description": "Increase vertical spacing for a more expansive presentation."
412
+ }
413
+ ]
414
+ },
415
+ "overrides": {
416
+ "description": "Focused typography overrides applied after the profile.",
417
+ "type": "object",
418
+ "properties": {
419
+ "headings": {
420
+ "description": "Overrides for heading levels.",
421
+ "type": "object",
422
+ "properties": {
423
+ "h1": {
424
+ "description": "Overrides for Markdown level 1 headings.",
425
+ "type": "object",
426
+ "properties": {
427
+ "align": {
428
+ "description": "Responsive text alignment override.",
429
+ "type": "object",
430
+ "properties": {
431
+ "desktop": {
432
+ "description": "Text alignment on wider screens.",
433
+ "type": "string",
434
+ "oneOf": [
435
+ {
436
+ "const": "left",
437
+ "title": "Left",
438
+ "description": "Align text with the left edge of its text area."
439
+ },
440
+ {
441
+ "const": "center",
442
+ "title": "Center",
443
+ "description": "Center text within its text area."
444
+ },
445
+ {
446
+ "const": "right",
447
+ "title": "Right",
448
+ "description": "Align text with the right edge of its text area."
449
+ }
450
+ ]
451
+ },
452
+ "mobile": {
453
+ "description": "Text alignment on narrow screens.",
454
+ "type": "string",
455
+ "oneOf": [
456
+ {
457
+ "const": "left",
458
+ "title": "Left",
459
+ "description": "Align text with the left edge of its text area."
460
+ },
461
+ {
462
+ "const": "center",
463
+ "title": "Center",
464
+ "description": "Center text within its text area."
465
+ },
466
+ {
467
+ "const": "right",
468
+ "title": "Right",
469
+ "description": "Align text with the right edge of its text area."
470
+ }
471
+ ]
472
+ }
473
+ },
474
+ "additionalProperties": false
475
+ },
476
+ "size": {
477
+ "type": "string",
478
+ "description": "Text size from the active typography system. Omit an override to keep the profile value.",
479
+ "oneOf": [
480
+ {
481
+ "const": "small",
482
+ "title": "Small",
483
+ "description": "Use the small type size from the active typography system."
484
+ },
485
+ {
486
+ "const": "medium",
487
+ "title": "Medium",
488
+ "description": "Use the standard type size from the active typography system."
489
+ },
490
+ {
491
+ "const": "large",
492
+ "title": "Large",
493
+ "description": "Use a larger type size for stronger emphasis."
494
+ },
495
+ {
496
+ "const": "xlarge",
497
+ "title": "Extra large",
498
+ "description": "Use the largest available type size for exceptional emphasis."
499
+ }
500
+ ]
501
+ },
502
+ "lineHeight": {
503
+ "type": "number",
504
+ "minimum": 1,
505
+ "maximum": 3,
506
+ "description": "Unitless line height between 1 and 3."
507
+ },
508
+ "weight": {
509
+ "description": "Heading font weight. Omit this to keep the profile value.",
510
+ "oneOf": [
511
+ {
512
+ "const": 400,
513
+ "title": "Regular",
514
+ "description": "Use regular font weight."
515
+ },
516
+ {
517
+ "const": 500,
518
+ "title": "Medium",
519
+ "description": "Use medium font weight."
520
+ },
521
+ {
522
+ "const": 600,
523
+ "title": "Semibold",
524
+ "description": "Use semibold font weight."
525
+ },
526
+ {
527
+ "const": 700,
528
+ "title": "Bold",
529
+ "description": "Use bold font weight."
530
+ }
531
+ ]
532
+ },
533
+ "spacingBefore": {
534
+ "description": "Vertical space before the heading.",
535
+ "type": "string",
536
+ "pattern": "^(?:0|(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|ch|lh))$"
537
+ },
538
+ "spacingAfter": {
539
+ "description": "Vertical space after the heading.",
540
+ "type": "string",
541
+ "pattern": "^(?:0|(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|ch|lh))$"
542
+ }
543
+ },
544
+ "additionalProperties": false
545
+ },
546
+ "h2": {
547
+ "description": "Overrides for Markdown level 2 section headings.",
548
+ "type": "object",
549
+ "properties": {
550
+ "align": {
551
+ "description": "Responsive text alignment override.",
552
+ "type": "object",
553
+ "properties": {
554
+ "desktop": {
555
+ "description": "Text alignment on wider screens.",
556
+ "type": "string",
557
+ "oneOf": [
558
+ {
559
+ "const": "left",
560
+ "title": "Left",
561
+ "description": "Align text with the left edge of its text area."
562
+ },
563
+ {
564
+ "const": "center",
565
+ "title": "Center",
566
+ "description": "Center text within its text area."
567
+ },
568
+ {
569
+ "const": "right",
570
+ "title": "Right",
571
+ "description": "Align text with the right edge of its text area."
572
+ }
573
+ ]
574
+ },
575
+ "mobile": {
576
+ "description": "Text alignment on narrow screens.",
577
+ "type": "string",
578
+ "oneOf": [
579
+ {
580
+ "const": "left",
581
+ "title": "Left",
582
+ "description": "Align text with the left edge of its text area."
583
+ },
584
+ {
585
+ "const": "center",
586
+ "title": "Center",
587
+ "description": "Center text within its text area."
588
+ },
589
+ {
590
+ "const": "right",
591
+ "title": "Right",
592
+ "description": "Align text with the right edge of its text area."
593
+ }
594
+ ]
595
+ }
596
+ },
597
+ "additionalProperties": false
598
+ },
599
+ "size": {
600
+ "type": "string",
601
+ "description": "Text size from the active typography system. Omit an override to keep the profile value.",
602
+ "oneOf": [
603
+ {
604
+ "const": "small",
605
+ "title": "Small",
606
+ "description": "Use the small type size from the active typography system."
607
+ },
608
+ {
609
+ "const": "medium",
610
+ "title": "Medium",
611
+ "description": "Use the standard type size from the active typography system."
612
+ },
613
+ {
614
+ "const": "large",
615
+ "title": "Large",
616
+ "description": "Use a larger type size for stronger emphasis."
617
+ },
618
+ {
619
+ "const": "xlarge",
620
+ "title": "Extra large",
621
+ "description": "Use the largest available type size for exceptional emphasis."
622
+ }
623
+ ]
624
+ },
625
+ "lineHeight": {
626
+ "type": "number",
627
+ "minimum": 1,
628
+ "maximum": 3,
629
+ "description": "Unitless line height between 1 and 3."
630
+ },
631
+ "weight": {
632
+ "description": "Heading font weight. Omit this to keep the profile value.",
633
+ "oneOf": [
634
+ {
635
+ "const": 400,
636
+ "title": "Regular",
637
+ "description": "Use regular font weight."
638
+ },
639
+ {
640
+ "const": 500,
641
+ "title": "Medium",
642
+ "description": "Use medium font weight."
643
+ },
644
+ {
645
+ "const": 600,
646
+ "title": "Semibold",
647
+ "description": "Use semibold font weight."
648
+ },
649
+ {
650
+ "const": 700,
651
+ "title": "Bold",
652
+ "description": "Use bold font weight."
653
+ }
654
+ ]
655
+ },
656
+ "spacingBefore": {
657
+ "description": "Vertical space before the heading.",
658
+ "type": "string",
659
+ "pattern": "^(?:0|(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|ch|lh))$"
660
+ },
661
+ "spacingAfter": {
662
+ "description": "Vertical space after the heading.",
663
+ "type": "string",
664
+ "pattern": "^(?:0|(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|ch|lh))$"
665
+ }
666
+ },
667
+ "additionalProperties": false
668
+ },
669
+ "h3": {
670
+ "description": "Overrides for Markdown level 3 headings.",
671
+ "type": "object",
672
+ "properties": {
673
+ "align": {
674
+ "description": "Responsive text alignment override.",
675
+ "type": "object",
676
+ "properties": {
677
+ "desktop": {
678
+ "description": "Text alignment on wider screens.",
679
+ "type": "string",
680
+ "oneOf": [
681
+ {
682
+ "const": "left",
683
+ "title": "Left",
684
+ "description": "Align text with the left edge of its text area."
685
+ },
686
+ {
687
+ "const": "center",
688
+ "title": "Center",
689
+ "description": "Center text within its text area."
690
+ },
691
+ {
692
+ "const": "right",
693
+ "title": "Right",
694
+ "description": "Align text with the right edge of its text area."
695
+ }
696
+ ]
697
+ },
698
+ "mobile": {
699
+ "description": "Text alignment on narrow screens.",
700
+ "type": "string",
701
+ "oneOf": [
702
+ {
703
+ "const": "left",
704
+ "title": "Left",
705
+ "description": "Align text with the left edge of its text area."
706
+ },
707
+ {
708
+ "const": "center",
709
+ "title": "Center",
710
+ "description": "Center text within its text area."
711
+ },
712
+ {
713
+ "const": "right",
714
+ "title": "Right",
715
+ "description": "Align text with the right edge of its text area."
716
+ }
717
+ ]
718
+ }
719
+ },
720
+ "additionalProperties": false
721
+ },
722
+ "size": {
723
+ "type": "string",
724
+ "description": "Text size from the active typography system. Omit an override to keep the profile value.",
725
+ "oneOf": [
726
+ {
727
+ "const": "small",
728
+ "title": "Small",
729
+ "description": "Use the small type size from the active typography system."
730
+ },
731
+ {
732
+ "const": "medium",
733
+ "title": "Medium",
734
+ "description": "Use the standard type size from the active typography system."
735
+ },
736
+ {
737
+ "const": "large",
738
+ "title": "Large",
739
+ "description": "Use a larger type size for stronger emphasis."
740
+ },
741
+ {
742
+ "const": "xlarge",
743
+ "title": "Extra large",
744
+ "description": "Use the largest available type size for exceptional emphasis."
745
+ }
746
+ ]
747
+ },
748
+ "lineHeight": {
749
+ "type": "number",
750
+ "minimum": 1,
751
+ "maximum": 3,
752
+ "description": "Unitless line height between 1 and 3."
753
+ },
754
+ "weight": {
755
+ "description": "Heading font weight. Omit this to keep the profile value.",
756
+ "oneOf": [
757
+ {
758
+ "const": 400,
759
+ "title": "Regular",
760
+ "description": "Use regular font weight."
761
+ },
762
+ {
763
+ "const": 500,
764
+ "title": "Medium",
765
+ "description": "Use medium font weight."
766
+ },
767
+ {
768
+ "const": 600,
769
+ "title": "Semibold",
770
+ "description": "Use semibold font weight."
771
+ },
772
+ {
773
+ "const": 700,
774
+ "title": "Bold",
775
+ "description": "Use bold font weight."
776
+ }
777
+ ]
778
+ },
779
+ "spacingBefore": {
780
+ "description": "Vertical space before the heading.",
781
+ "type": "string",
782
+ "pattern": "^(?:0|(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|ch|lh))$"
783
+ },
784
+ "spacingAfter": {
785
+ "description": "Vertical space after the heading.",
786
+ "type": "string",
787
+ "pattern": "^(?:0|(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|ch|lh))$"
788
+ }
789
+ },
790
+ "additionalProperties": false
791
+ },
792
+ "h4": {
793
+ "description": "Overrides for Markdown level 4 headings.",
794
+ "type": "object",
795
+ "properties": {
796
+ "align": {
797
+ "description": "Responsive text alignment override.",
798
+ "type": "object",
799
+ "properties": {
800
+ "desktop": {
801
+ "description": "Text alignment on wider screens.",
802
+ "type": "string",
803
+ "oneOf": [
804
+ {
805
+ "const": "left",
806
+ "title": "Left",
807
+ "description": "Align text with the left edge of its text area."
808
+ },
809
+ {
810
+ "const": "center",
811
+ "title": "Center",
812
+ "description": "Center text within its text area."
813
+ },
814
+ {
815
+ "const": "right",
816
+ "title": "Right",
817
+ "description": "Align text with the right edge of its text area."
818
+ }
819
+ ]
820
+ },
821
+ "mobile": {
822
+ "description": "Text alignment on narrow screens.",
823
+ "type": "string",
824
+ "oneOf": [
825
+ {
826
+ "const": "left",
827
+ "title": "Left",
828
+ "description": "Align text with the left edge of its text area."
829
+ },
830
+ {
831
+ "const": "center",
832
+ "title": "Center",
833
+ "description": "Center text within its text area."
834
+ },
835
+ {
836
+ "const": "right",
837
+ "title": "Right",
838
+ "description": "Align text with the right edge of its text area."
839
+ }
840
+ ]
841
+ }
842
+ },
843
+ "additionalProperties": false
844
+ },
845
+ "size": {
846
+ "type": "string",
847
+ "description": "Text size from the active typography system. Omit an override to keep the profile value.",
848
+ "oneOf": [
849
+ {
850
+ "const": "small",
851
+ "title": "Small",
852
+ "description": "Use the small type size from the active typography system."
853
+ },
854
+ {
855
+ "const": "medium",
856
+ "title": "Medium",
857
+ "description": "Use the standard type size from the active typography system."
858
+ },
859
+ {
860
+ "const": "large",
861
+ "title": "Large",
862
+ "description": "Use a larger type size for stronger emphasis."
863
+ },
864
+ {
865
+ "const": "xlarge",
866
+ "title": "Extra large",
867
+ "description": "Use the largest available type size for exceptional emphasis."
868
+ }
869
+ ]
870
+ },
871
+ "lineHeight": {
872
+ "type": "number",
873
+ "minimum": 1,
874
+ "maximum": 3,
875
+ "description": "Unitless line height between 1 and 3."
876
+ },
877
+ "weight": {
878
+ "description": "Heading font weight. Omit this to keep the profile value.",
879
+ "oneOf": [
880
+ {
881
+ "const": 400,
882
+ "title": "Regular",
883
+ "description": "Use regular font weight."
884
+ },
885
+ {
886
+ "const": 500,
887
+ "title": "Medium",
888
+ "description": "Use medium font weight."
889
+ },
890
+ {
891
+ "const": 600,
892
+ "title": "Semibold",
893
+ "description": "Use semibold font weight."
894
+ },
895
+ {
896
+ "const": 700,
897
+ "title": "Bold",
898
+ "description": "Use bold font weight."
899
+ }
900
+ ]
901
+ },
902
+ "spacingBefore": {
903
+ "description": "Vertical space before the heading.",
904
+ "type": "string",
905
+ "pattern": "^(?:0|(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|ch|lh))$"
906
+ },
907
+ "spacingAfter": {
908
+ "description": "Vertical space after the heading.",
909
+ "type": "string",
910
+ "pattern": "^(?:0|(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|ch|lh))$"
911
+ }
912
+ },
913
+ "additionalProperties": false
914
+ }
915
+ },
916
+ "additionalProperties": false
917
+ },
918
+ "body": {
919
+ "description": "Overrides for body text.",
920
+ "type": "object",
921
+ "properties": {
922
+ "align": {
923
+ "description": "Responsive text alignment override.",
924
+ "type": "object",
925
+ "properties": {
926
+ "desktop": {
927
+ "description": "Text alignment on wider screens.",
928
+ "type": "string",
929
+ "oneOf": [
930
+ {
931
+ "const": "left",
932
+ "title": "Left",
933
+ "description": "Align text with the left edge of its text area."
934
+ },
935
+ {
936
+ "const": "center",
937
+ "title": "Center",
938
+ "description": "Center text within its text area."
939
+ },
940
+ {
941
+ "const": "right",
942
+ "title": "Right",
943
+ "description": "Align text with the right edge of its text area."
944
+ }
945
+ ]
946
+ },
947
+ "mobile": {
948
+ "description": "Text alignment on narrow screens.",
949
+ "type": "string",
950
+ "oneOf": [
951
+ {
952
+ "const": "left",
953
+ "title": "Left",
954
+ "description": "Align text with the left edge of its text area."
955
+ },
956
+ {
957
+ "const": "center",
958
+ "title": "Center",
959
+ "description": "Center text within its text area."
960
+ },
961
+ {
962
+ "const": "right",
963
+ "title": "Right",
964
+ "description": "Align text with the right edge of its text area."
965
+ }
966
+ ]
967
+ }
968
+ },
969
+ "additionalProperties": false
970
+ },
971
+ "size": {
972
+ "type": "string",
973
+ "description": "Text size from the active typography system. Omit an override to keep the profile value.",
974
+ "oneOf": [
975
+ {
976
+ "const": "small",
977
+ "title": "Small",
978
+ "description": "Use the small type size from the active typography system."
979
+ },
980
+ {
981
+ "const": "medium",
982
+ "title": "Medium",
983
+ "description": "Use the standard type size from the active typography system."
984
+ },
985
+ {
986
+ "const": "large",
987
+ "title": "Large",
988
+ "description": "Use a larger type size for stronger emphasis."
989
+ },
990
+ {
991
+ "const": "xlarge",
992
+ "title": "Extra large",
993
+ "description": "Use the largest available type size for exceptional emphasis."
994
+ }
995
+ ]
996
+ },
997
+ "lineHeight": {
998
+ "type": "number",
999
+ "minimum": 1,
1000
+ "maximum": 3,
1001
+ "description": "Unitless line height between 1 and 3."
1002
+ },
1003
+ "width": {
1004
+ "description": "Maximum body-text line length.",
1005
+ "type": "string",
1006
+ "oneOf": [
1007
+ {
1008
+ "const": "narrow",
1009
+ "title": "Narrow",
1010
+ "description": "Use a shorter line length suited to reading-focused text."
1011
+ },
1012
+ {
1013
+ "const": "normal",
1014
+ "title": "Normal",
1015
+ "description": "Use the balanced default line length."
1016
+ },
1017
+ {
1018
+ "const": "wide",
1019
+ "title": "Wide",
1020
+ "description": "Allow body text to use more horizontal space."
1021
+ }
1022
+ ]
1023
+ },
1024
+ "paragraphSpacing": {
1025
+ "description": "Vertical space between body paragraphs.",
1026
+ "type": "string",
1027
+ "pattern": "^(?:0|(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|ch|lh))$"
1028
+ }
1029
+ },
1030
+ "additionalProperties": false
1031
+ },
1032
+ "caption": {
1033
+ "description": "Overrides for image captions.",
1034
+ "type": "object",
1035
+ "properties": {
1036
+ "align": {
1037
+ "description": "Responsive text alignment override.",
1038
+ "type": "object",
1039
+ "properties": {
1040
+ "desktop": {
1041
+ "description": "Text alignment on wider screens.",
1042
+ "type": "string",
1043
+ "oneOf": [
1044
+ {
1045
+ "const": "left",
1046
+ "title": "Left",
1047
+ "description": "Align text with the left edge of its text area."
1048
+ },
1049
+ {
1050
+ "const": "center",
1051
+ "title": "Center",
1052
+ "description": "Center text within its text area."
1053
+ },
1054
+ {
1055
+ "const": "right",
1056
+ "title": "Right",
1057
+ "description": "Align text with the right edge of its text area."
1058
+ }
1059
+ ]
1060
+ },
1061
+ "mobile": {
1062
+ "description": "Text alignment on narrow screens.",
1063
+ "type": "string",
1064
+ "oneOf": [
1065
+ {
1066
+ "const": "left",
1067
+ "title": "Left",
1068
+ "description": "Align text with the left edge of its text area."
1069
+ },
1070
+ {
1071
+ "const": "center",
1072
+ "title": "Center",
1073
+ "description": "Center text within its text area."
1074
+ },
1075
+ {
1076
+ "const": "right",
1077
+ "title": "Right",
1078
+ "description": "Align text with the right edge of its text area."
1079
+ }
1080
+ ]
1081
+ }
1082
+ },
1083
+ "additionalProperties": false
1084
+ },
1085
+ "size": {
1086
+ "type": "string",
1087
+ "description": "Text size from the active typography system. Omit an override to keep the profile value.",
1088
+ "oneOf": [
1089
+ {
1090
+ "const": "small",
1091
+ "title": "Small",
1092
+ "description": "Use the small type size from the active typography system."
1093
+ },
1094
+ {
1095
+ "const": "medium",
1096
+ "title": "Medium",
1097
+ "description": "Use the standard type size from the active typography system."
1098
+ },
1099
+ {
1100
+ "const": "large",
1101
+ "title": "Large",
1102
+ "description": "Use a larger type size for stronger emphasis."
1103
+ },
1104
+ {
1105
+ "const": "xlarge",
1106
+ "title": "Extra large",
1107
+ "description": "Use the largest available type size for exceptional emphasis."
1108
+ }
1109
+ ]
1110
+ },
1111
+ "lineHeight": {
1112
+ "type": "number",
1113
+ "minimum": 1,
1114
+ "maximum": 3,
1115
+ "description": "Unitless line height between 1 and 3."
1116
+ },
1117
+ "spacingBefore": {
1118
+ "description": "Vertical space between an image and its caption.",
1119
+ "type": "string",
1120
+ "pattern": "^(?:0|(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|ch|lh))$"
1121
+ }
1122
+ },
1123
+ "additionalProperties": false
1124
+ }
1125
+ },
1126
+ "additionalProperties": false
1127
+ }
1128
+ },
1129
+ "additionalProperties": false,
1130
+ "description": "Typography profile, rhythm, font family, and focused overrides.",
1131
+ "markdownDescription": "```yaml\ntypography:\n profile: reading\n rhythm: normal\n```\n\nOverrides the preset's font stack, typography profile, rhythm or individual text settings.\n\n[Typography configuration reference](https://github.com/janga/norna/blob/v0.7.15/docs/typography.md#configuration-shape)"
1132
+ },
1133
+ "palette": {
1134
+ "type": "string",
1135
+ "description": "Coordinated site color palette. Omit this to use the selected preset.",
1136
+ "oneOf": [
1137
+ {
1138
+ "const": "dark",
1139
+ "title": "Dark",
1140
+ "description": "Black page and section surfaces with light text."
1141
+ },
1142
+ {
1143
+ "const": "light",
1144
+ "title": "Light",
1145
+ "description": "White and cool light surfaces with dark text."
1146
+ },
1147
+ {
1148
+ "const": "paper",
1149
+ "title": "Paper",
1150
+ "description": "Warm off-white surfaces with dark text."
1151
+ }
1152
+ ],
1153
+ "markdownDescription": "```yaml\npalette: paper\n```\n\nChooses a coordinated color system for the page frame, navigation, footer and section surfaces.\n\n[Palette and section surfaces](https://github.com/janga/norna/blob/v0.7.15/docs/theme.md#palette-and-section-surfaces)",
1154
+ "examples": [
1155
+ "dark",
1156
+ "light",
1157
+ "paper"
1158
+ ]
1159
+ },
1160
+ "sectionSurfaces": {
1161
+ "description": "Surface sequence cycled through page sections. Omit this to keep the selected preset.",
1162
+ "minItems": 1,
1163
+ "maxItems": 3,
1164
+ "type": "array",
1165
+ "items": {
1166
+ "type": "string",
1167
+ "description": "Semantic section surface from the active palette.",
1168
+ "oneOf": [
1169
+ {
1170
+ "const": "base",
1171
+ "title": "Base",
1172
+ "description": "Use the palette's normal page surface."
1173
+ },
1174
+ {
1175
+ "const": "soft",
1176
+ "title": "Soft",
1177
+ "description": "Use the palette's subtle contrasting surface."
1178
+ },
1179
+ {
1180
+ "const": "emphasis",
1181
+ "title": "Emphasis",
1182
+ "description": "Use the palette's strongest section surface."
1183
+ }
1184
+ ]
1185
+ },
1186
+ "markdownDescription": "```yaml\nsectionSurfaces: [base, soft, emphasis]\n```\n\nLists one to three semantic surfaces. Norna cycles through them in section order; omit this field to keep the preset sequence.\n\n[Palette and section surfaces](https://github.com/janga/norna/blob/v0.7.15/docs/theme.md#palette-and-section-surfaces)",
1187
+ "examples": [
1188
+ [
1189
+ "base"
1190
+ ],
1191
+ [
1192
+ "base",
1193
+ "soft"
1194
+ ],
1195
+ [
1196
+ "base",
1197
+ "soft",
1198
+ "emphasis"
1199
+ ]
1200
+ ]
1201
+ }
1202
+ },
1203
+ "additionalProperties": false,
1204
+ "description": "Visual settings for a site or route. A route theme replaces the inherited visual theme; site identity remains site-wide.",
1205
+ "$id": "https://janga.github.io/norna/schemas/theme.schema.json",
1206
+ "title": "Norna visual theme",
1207
+ "markdownDescription": "```yaml\npreset: documentation\n```\n\nChoose one complete preset first. Add focused overrides only when the site needs to differ from it.\n\n[Theme reference](https://github.com/janga/norna/blob/v0.7.15/docs/theme.md)"
1208
+ }