@janga/norna 0.7.16 → 0.7.18

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 (342) hide show
  1. package/README.md +18 -9
  2. package/astro.config.mjs +35 -3
  3. package/bin/norna-cli.mjs +1 -1
  4. package/docs/README.md +16 -14
  5. package/docs/commands.md +2 -2
  6. package/docs/configuration.md +25 -2
  7. package/docs/content.md +92 -103
  8. package/docs/design/navigation-and-theme-plan.md +186 -0
  9. package/docs/design/norna-diagram-design.md +9 -9
  10. package/docs/design/site-examples-structure.md +15 -11
  11. package/docs/engine-development.md +19 -22
  12. package/docs/getting-started.md +21 -16
  13. package/docs/images-and-metadata.md +12 -9
  14. package/docs/pages.md +190 -0
  15. package/docs/public-files.md +49 -36
  16. package/docs/publishing.md +19 -3
  17. package/docs/site-files.md +46 -38
  18. package/docs/sitewide-content.md +20 -36
  19. package/docs/theme.md +109 -143
  20. package/docs/typography.md +19 -31
  21. package/examples/README.md +10 -4
  22. package/examples/complete-sites/dog-shelter-multi-page/.astro/collections/site.schema.json +17 -41
  23. package/examples/complete-sites/dog-shelter-multi-page/.astro/collections/sitewide.schema.json +26 -67
  24. package/examples/complete-sites/dog-shelter-multi-page/.astro/collections/theme.schema.json +185 -88
  25. package/examples/complete-sites/dog-shelter-multi-page/.astro/data-store.json +1 -1
  26. package/examples/complete-sites/dog-shelter-multi-page/.astro/dev-local.json +4 -4
  27. package/examples/complete-sites/dog-shelter-multi-page/.astro/dev.json +5 -5
  28. package/examples/complete-sites/dog-shelter-multi-page/.astro/dev.log +33 -23
  29. package/examples/complete-sites/dog-shelter-multi-page/README.md +1 -1
  30. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/_metadata.json +8 -8
  31. package/examples/complete-sites/dog-shelter-multi-page/site/.norna/generated-images.json +20 -20
  32. package/examples/complete-sites/dog-shelter-multi-page/site/{content.md → pages/000-home/content.md} +3 -4
  33. package/examples/complete-sites/dog-shelter-multi-page/site/{routes → pages}/010-dogs/content.md +3 -4
  34. package/examples/complete-sites/dog-shelter-multi-page/site/{routes → pages}/020-adopt/content.md +3 -4
  35. package/examples/complete-sites/dog-shelter-multi-page/site/sitewide-content.yaml +2 -4
  36. package/examples/complete-sites/dog-shelter-single-page/.astro/collections/site.schema.json +17 -41
  37. package/examples/complete-sites/dog-shelter-single-page/.astro/collections/sitewide.schema.json +26 -67
  38. package/examples/complete-sites/dog-shelter-single-page/.astro/collections/theme.schema.json +185 -88
  39. package/examples/complete-sites/dog-shelter-single-page/.astro/data-store.json +1 -1
  40. package/examples/complete-sites/dog-shelter-single-page/.astro/dev-local.json +8 -0
  41. package/examples/complete-sites/dog-shelter-single-page/.astro/dev.json +5 -5
  42. package/examples/complete-sites/dog-shelter-single-page/.astro/dev.log +16 -12
  43. package/examples/complete-sites/dog-shelter-single-page/README.md +1 -1
  44. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/_metadata.json +8 -8
  45. package/examples/complete-sites/dog-shelter-single-page/site/.norna/generated-images.json +12 -12
  46. package/examples/complete-sites/dog-shelter-single-page/site/{content.md → pages/000-home/content.md} +5 -2
  47. package/examples/complete-sites/dog-shelter-single-page/site/sitewide-content.yaml +2 -4
  48. package/examples/feature-demos/media-and-surfaces/.astro/collections/site.schema.json +17 -41
  49. package/examples/feature-demos/media-and-surfaces/.astro/collections/sitewide.schema.json +26 -67
  50. package/examples/feature-demos/media-and-surfaces/.astro/collections/theme.schema.json +180 -91
  51. package/examples/feature-demos/media-and-surfaces/.astro/data-store.json +1 -1
  52. package/examples/feature-demos/media-and-surfaces/.astro/dev.log +4 -14
  53. package/examples/feature-demos/media-and-surfaces/README.md +2 -2
  54. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/_metadata.json +9 -9
  55. package/examples/feature-demos/media-and-surfaces/site/.norna/generated-images.json +16 -16
  56. package/examples/feature-demos/media-and-surfaces/site/{content.md → pages/000-home/content.md} +4 -7
  57. package/examples/feature-demos/media-and-surfaces/site/{routes → pages}/010-media/content.md +6 -5
  58. package/examples/feature-demos/media-and-surfaces/site/{routes → pages}/020-surfaces/content.md +9 -7
  59. package/examples/feature-demos/media-and-surfaces/site/pages/020-surfaces/theme.yaml +2 -0
  60. package/examples/feature-demos/media-and-surfaces/site/sitewide-content.yaml +0 -2
  61. package/examples/feature-demos/sitewide-content/README.md +3 -3
  62. package/examples/feature-demos/sitewide-content/site/pages/000-home/content.md +17 -0
  63. package/examples/feature-demos/sitewide-content/site/pages/010-logo-and-navigation/content.md +17 -0
  64. package/examples/feature-demos/sitewide-content/site/{routes → pages}/020-notices/content.md +7 -6
  65. package/examples/feature-demos/sitewide-content/site/{routes → pages}/030-footer/content.md +6 -5
  66. package/examples/feature-demos/sitewide-content/site/sitewide-content.yaml +4 -13
  67. package/examples/feature-demos/theme-preset-documentation/README.md +16 -0
  68. package/examples/feature-demos/theme-preset-documentation/site/.norna/generated-images.json +74 -0
  69. package/examples/feature-demos/{theme-presets/site/routes/020-documentation → theme-preset-documentation/site/pages/000-home}/content.md +4 -7
  70. package/examples/feature-demos/theme-preset-documentation/site/sitewide-content.yaml +3 -0
  71. package/examples/feature-demos/theme-preset-portfolio/README.md +16 -0
  72. package/examples/feature-demos/theme-preset-portfolio/site/.norna/generated-images.json +74 -0
  73. package/examples/feature-demos/theme-preset-portfolio/site/config.yaml +1 -0
  74. package/examples/feature-demos/{theme-presets/site/routes/010-portfolio → theme-preset-portfolio/site/pages/000-home}/content.md +4 -7
  75. package/examples/feature-demos/theme-preset-portfolio/site/public/robots.txt +2 -0
  76. package/examples/feature-demos/theme-preset-portfolio/site/sitewide-content.yaml +3 -0
  77. package/examples/feature-demos/theme-preset-project/README.md +16 -0
  78. package/examples/feature-demos/theme-preset-project/site/.norna/generated-images.json +74 -0
  79. package/examples/feature-demos/theme-preset-project/site/config.yaml +1 -0
  80. package/examples/feature-demos/{theme-presets/site/routes/030-project → theme-preset-project/site/pages/000-home}/content.md +4 -7
  81. package/examples/feature-demos/theme-preset-project/site/public/robots.txt +2 -0
  82. package/examples/feature-demos/theme-preset-project/site/sitewide-content.yaml +3 -0
  83. package/examples/feature-demos/theme-preset-statement/README.md +16 -0
  84. package/examples/feature-demos/theme-preset-statement/site/.norna/generated-images.json +74 -0
  85. package/examples/feature-demos/theme-preset-statement/site/config.yaml +1 -0
  86. package/examples/feature-demos/{theme-presets/site/routes/040-statement → theme-preset-statement/site/pages/000-home}/content.md +4 -7
  87. package/examples/feature-demos/theme-preset-statement/site/public/robots.txt +2 -0
  88. package/examples/feature-demos/theme-preset-statement/site/sitewide-content.yaml +3 -0
  89. package/fixtures/basic/site/{content.md → pages/000-home/content.md} +5 -6
  90. package/fixtures/basic/site/sitewide-content.yaml +1 -8
  91. package/fixtures/basic/site/theme.yaml +1 -8
  92. package/fixtures/content-model-v2/site/{content.md → pages/000-home/content.md} +11 -13
  93. package/fixtures/content-model-v2/site/pages/010-guide/content.md +18 -0
  94. package/fixtures/nested-pages/.astro/collections/site.schema.json +35 -0
  95. package/fixtures/nested-pages/.astro/collections/sitewide.schema.json +97 -0
  96. package/fixtures/nested-pages/.astro/collections/theme.schema.json +844 -0
  97. package/fixtures/nested-pages/.astro/content-assets.mjs +4 -0
  98. package/fixtures/nested-pages/.astro/content-modules.mjs +1 -0
  99. package/fixtures/nested-pages/.astro/content.d.ts +183 -0
  100. package/fixtures/nested-pages/.astro/data-store.json +1 -0
  101. package/fixtures/nested-pages/.astro/dev-local.json +8 -0
  102. package/fixtures/nested-pages/.astro/dev.log +1128 -0
  103. package/fixtures/nested-pages/.astro/settings.json +5 -0
  104. package/fixtures/nested-pages/.astro/types.d.ts +2 -0
  105. package/fixtures/nested-pages/node_modules/.vite/deps/_metadata.json +74 -0
  106. package/fixtures/nested-pages/node_modules/.vite/deps/astro-D_GIpJIE.js +434 -0
  107. package/fixtures/nested-pages/node_modules/.vite/deps/astro-D_GIpJIE.js.map +1 -0
  108. package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_aria-query.js +6390 -0
  109. package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_aria-query.js.map +1 -0
  110. package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_axobject-query.js +2702 -0
  111. package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_axobject-query.js.map +1 -0
  112. package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_html-escaper.js +65 -0
  113. package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_html-escaper.js.map +1 -0
  114. package/fixtures/nested-pages/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +455 -0
  115. package/fixtures/nested-pages/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +1 -0
  116. package/fixtures/nested-pages/node_modules/.vite/deps/audit-DM9Hkl5B.js +1534 -0
  117. package/fixtures/nested-pages/node_modules/.vite/deps/audit-DM9Hkl5B.js.map +1 -0
  118. package/fixtures/nested-pages/node_modules/.vite/deps/highlight-DnZ1ULOL.js +48 -0
  119. package/fixtures/nested-pages/node_modules/.vite/deps/highlight-DnZ1ULOL.js.map +1 -0
  120. package/fixtures/nested-pages/node_modules/.vite/deps/icons-RyebKi45.js +42 -0
  121. package/fixtures/nested-pages/node_modules/.vite/deps/icons-RyebKi45.js.map +1 -0
  122. package/fixtures/nested-pages/node_modules/.vite/deps/package.json +3 -0
  123. package/fixtures/nested-pages/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +4 -0
  124. package/fixtures/nested-pages/node_modules/.vite/deps/settings-DaU-gNnK.js +190 -0
  125. package/fixtures/nested-pages/node_modules/.vite/deps/settings-DaU-gNnK.js.map +1 -0
  126. package/fixtures/nested-pages/node_modules/.vite/deps/toolbar-63PA619H.js +458 -0
  127. package/fixtures/nested-pages/node_modules/.vite/deps/toolbar-63PA619H.js.map +1 -0
  128. package/fixtures/nested-pages/node_modules/.vite/deps/ui-library--bkcNSYz.js +1016 -0
  129. package/fixtures/nested-pages/node_modules/.vite/deps/ui-library--bkcNSYz.js.map +1 -0
  130. package/fixtures/nested-pages/node_modules/.vite/deps/window-jPkpKVcy.js +32 -0
  131. package/fixtures/nested-pages/node_modules/.vite/deps/window-jPkpKVcy.js.map +1 -0
  132. package/fixtures/nested-pages/node_modules/.vite/deps/xray-BXCHpEJP.js +111 -0
  133. package/fixtures/nested-pages/node_modules/.vite/deps/xray-BXCHpEJP.js.map +1 -0
  134. package/fixtures/nested-pages/site/.norna/generated-images.json +10 -0
  135. package/fixtures/nested-pages/site/config.yaml +3 -0
  136. package/fixtures/nested-pages/site/pages/000-home/content.md +15 -0
  137. package/fixtures/nested-pages/site/pages/010-guides/content.md +15 -0
  138. package/fixtures/nested-pages/site/pages/010-guides/pages/010-installation/content.md +21 -0
  139. package/fixtures/nested-pages/site/pages/010-guides/pages/010-installation/images/diagram.svg +21 -0
  140. package/fixtures/nested-pages/site/pages/010-guides/pages/010-installation/pages/010-macos/content.md +18 -0
  141. package/fixtures/nested-pages/site/pages/010-guides/pages/010-installation/theme.yaml +2 -0
  142. package/fixtures/nested-pages/site/pages/010-guides/pages/020-workflows/content.md +11 -0
  143. package/fixtures/nested-pages/site/pages/010-guides/pages/030-release-notes/content.md +9 -0
  144. package/fixtures/nested-pages/site/pages/010-guides/pages/040-configuration/content.md +3 -0
  145. package/fixtures/nested-pages/site/pages/010-guides/pages/050-content/content.md +3 -0
  146. package/fixtures/nested-pages/site/pages/010-guides/pages/060-themes/content.md +3 -0
  147. package/fixtures/nested-pages/site/pages/010-guides/pages/070-images/content.md +3 -0
  148. package/fixtures/nested-pages/site/pages/010-guides/pages/080-cards/content.md +3 -0
  149. package/fixtures/nested-pages/site/pages/010-guides/pages/090-notes/content.md +3 -0
  150. package/fixtures/nested-pages/site/pages/010-guides/pages/100-validation/content.md +3 -0
  151. package/fixtures/nested-pages/site/pages/010-guides/pages/110-synchronization/content.md +3 -0
  152. package/fixtures/nested-pages/site/pages/010-guides/pages/120-building/content.md +3 -0
  153. package/fixtures/nested-pages/site/pages/010-guides/pages/130-publishing/content.md +3 -0
  154. package/fixtures/nested-pages/site/pages/010-guides/pages/140-troubleshooting/content.md +3 -0
  155. package/fixtures/nested-pages/site/pages/010-guides/pages/150-migration/content.md +3 -0
  156. package/fixtures/nested-pages/site/pages/010-guides/pages/160-accessibility/content.md +3 -0
  157. package/fixtures/nested-pages/site/pages/010-guides/pages/170-performance/content.md +3 -0
  158. package/fixtures/nested-pages/site/pages/010-guides/pages/180-security/content.md +3 -0
  159. package/fixtures/nested-pages/site/pages/010-guides/pages/190-localization/content.md +3 -0
  160. package/fixtures/nested-pages/site/pages/010-guides/pages/200-upgrading/content.md +3 -0
  161. package/fixtures/nested-pages/site/pages/010-guides/pages/210-faq/content.md +3 -0
  162. package/fixtures/nested-pages/site/pages/010-guides/theme.yaml +7 -0
  163. package/fixtures/nested-pages/site/pages/020-reference/content.md +10 -0
  164. package/fixtures/nested-pages/site/pages/020-reference/pages/010-installation/content.md +11 -0
  165. package/fixtures/nested-pages/site/theme.yaml +1 -0
  166. package/package.json +8 -4
  167. package/schemas/config.schema.json +46 -5
  168. package/schemas/content-frontmatter.schema.json +28 -63
  169. package/schemas/manifest.json +4 -2
  170. package/schemas/page-theme.schema.json +192 -0
  171. package/schemas/sitewide-content.schema.json +58 -132
  172. package/schemas/theme.schema.json +434 -152
  173. package/scripts/check-config.mjs +8 -6
  174. package/scripts/deploy-site.mjs +6 -8
  175. package/scripts/doctor.mjs +2 -2
  176. package/scripts/generate-images.mjs +15 -29
  177. package/scripts/generate-schemas.mjs +7 -218
  178. package/scripts/lib/base-path-redirect.mjs +9 -0
  179. package/scripts/lib/editor-language-service.mjs +125 -79
  180. package/scripts/lib/example-sites.mjs +15 -3
  181. package/scripts/lib/heading-ids.mjs +161 -0
  182. package/scripts/lib/navigation-model.mjs +51 -0
  183. package/scripts/lib/norna-markdown-blocks.mjs +3 -4
  184. package/scripts/lib/page-model.mjs +102 -0
  185. package/scripts/lib/presentation.mjs +25 -17
  186. package/scripts/lib/project-config.mjs +46 -8
  187. package/scripts/lib/schema-definitions.mjs +78 -55
  188. package/scripts/lib/schema-editor-metadata.mjs +519 -0
  189. package/scripts/lib/schema-value-definitions.mjs +20 -6
  190. package/scripts/lib/site-content.mjs +159 -97
  191. package/scripts/lib/site-conventions.mjs +1 -0
  192. package/scripts/lib/site-paths.mjs +11 -5
  193. package/scripts/lib/theme-config.mjs +12 -18
  194. package/scripts/lib/theme-presets.mjs +55 -14
  195. package/scripts/release.mjs +1 -1
  196. package/scripts/show-typography.mjs +4 -30
  197. package/scripts/sync-content-sections.mjs +107 -68
  198. package/scripts/{test-temporary-visibility.mjs → test-banner-visibility.mjs} +8 -25
  199. package/scripts/test-client-javascript.mjs +50 -46
  200. package/scripts/test-content-check.mjs +153 -69
  201. package/scripts/test-content-model-v2.mjs +566 -285
  202. package/scripts/test-documentation.mjs +10 -3
  203. package/scripts/test-editor-language-service.mjs +172 -25
  204. package/scripts/test-engine-commands.mjs +6 -5
  205. package/scripts/test-examples.mjs +30 -1
  206. package/scripts/test-heading-ids.mjs +68 -0
  207. package/scripts/test-navigation-model.mjs +63 -0
  208. package/scripts/test-navigation.mjs +11 -2
  209. package/scripts/test-nested-pages.mjs +125 -0
  210. package/scripts/test-package-check.mjs +55 -95
  211. package/scripts/test-page-model.mjs +108 -0
  212. package/scripts/test-project-config.mjs +22 -3
  213. package/scripts/test-schemas.mjs +82 -15
  214. package/scripts/test-theme-presets.mjs +57 -42
  215. package/src/components/NavigationPageTree.astro +157 -0
  216. package/src/components/PageContentsLinks.astro +40 -0
  217. package/src/components/SectionNavigationScript.astro +117 -5
  218. package/src/components/SiteBreadcrumbs.astro +32 -0
  219. package/src/components/SiteNavigation.astro +97 -68
  220. package/src/components/SitePage.astro +93 -55
  221. package/src/components/SiteSection.astro +29 -27
  222. package/src/components/SiteTreeNavigation.astro +35 -0
  223. package/src/components/TreeNavigationScript.astro +83 -0
  224. package/src/content.config.ts +8 -10
  225. package/src/layouts/BaseLayout.astro +21 -20
  226. package/src/lib/pageThemes.ts +44 -0
  227. package/src/lib/sectionContent.ts +106 -107
  228. package/src/lib/siteNavigation.ts +49 -0
  229. package/src/lib/sitePages.ts +96 -38
  230. package/src/pages/{[slug].astro → [...slug].astro} +2 -2
  231. package/src/pages/index.astro +2 -1
  232. package/src/styles/global.css +743 -57
  233. package/starters/basic/README.md +9 -11
  234. package/starters/basic/site/{content.md → pages/000-home/content.md} +7 -4
  235. package/starters/basic/site/sitewide-content.yaml +1 -10
  236. package/starters/basic/site/theme.yaml +1 -105
  237. package/starters/project/README.md +14 -14
  238. package/starters/project/site/{content.md → pages/000-home/content.md} +5 -4
  239. package/starters/project/site/{routes → pages}/010-guide/content.md +5 -4
  240. package/starters/project/site/sitewide-content.yaml +1 -10
  241. package/starters/project/site/theme.yaml +1 -31
  242. package/docs/routes.md +0 -121
  243. package/examples/feature-demos/media-and-surfaces/site/routes/020-surfaces/theme.yaml +0 -3
  244. package/examples/feature-demos/sitewide-content/site/content.md +0 -16
  245. package/examples/feature-demos/sitewide-content/site/routes/010-identity/content.md +0 -20
  246. package/examples/feature-demos/theme-presets/README.md +0 -18
  247. package/examples/feature-demos/theme-presets/site/.norna/generated-images.json +0 -290
  248. package/examples/feature-demos/theme-presets/site/content.md +0 -17
  249. package/examples/feature-demos/theme-presets/site/sitewide-content.yaml +0 -12
  250. package/examples/feature-demos/theme-presets/site/theme.yaml +0 -1
  251. package/fixtures/content-model-v2/site/routes/010-guide/content.md +0 -17
  252. package/scripts/lib/route-model.mjs +0 -17
  253. package/scripts/test-route-model.mjs +0 -32
  254. package/src/lib/routeThemes.ts +0 -41
  255. /package/examples/complete-sites/dog-shelter-multi-page/site/{images/intro → pages/000-home/images}/shelter-dog.jpg +0 -0
  256. /package/examples/complete-sites/dog-shelter-multi-page/site/{routes/010-dogs/images/dogs → pages/010-dogs/images}/rex.jpg +0 -0
  257. /package/examples/complete-sites/dog-shelter-multi-page/site/{routes/010-dogs/images/dogs → pages/010-dogs/images}/rover.jpg +0 -0
  258. /package/examples/complete-sites/dog-shelter-multi-page/site/{routes/020-adopt/images/adopt → pages/020-adopt/images}/adoption-dog.jpg +0 -0
  259. /package/examples/complete-sites/dog-shelter-single-page/site/{images/you-can-help → pages/000-home/images}/foster-dog.jpg +0 -0
  260. /package/examples/complete-sites/dog-shelter-single-page/site/{images/what-we-do → pages/000-home/images}/rescue-dog.jpg +0 -0
  261. /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/cards → pages/010-media/images}/card-carousel.svg +0 -0
  262. /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/cards → pages/010-media/images}/card-stack.svg +0 -0
  263. /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/cards → pages/010-media/images}/card-surfaces.svg +0 -0
  264. /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/carousel → pages/010-media/images}/carousel-one.svg +0 -0
  265. /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/carousel → pages/010-media/images}/carousel-three.svg +0 -0
  266. /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/carousel → pages/010-media/images}/carousel-two.svg +0 -0
  267. /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/stack → pages/010-media/images}/stack-one.svg +0 -0
  268. /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/stack → pages/010-media/images}/stack-two.svg +0 -0
  269. /package/examples/feature-demos/{theme-presets → theme-preset-documentation}/site/config.yaml +0 -0
  270. /package/examples/feature-demos/{theme-presets/site/routes/020-documentation/images/documentation → theme-preset-documentation/site/pages/000-home/images}/black-schnauzer.jpg +0 -0
  271. /package/examples/feature-demos/{theme-presets/site/routes/020-documentation/images/documentation → theme-preset-documentation/site/pages/000-home/images}/retriever-portrait.jpg +0 -0
  272. /package/examples/feature-demos/{theme-presets/site/routes/020-documentation/images/documentation → theme-preset-documentation/site/pages/000-home/images}/schnauzer-portrait.jpg +0 -0
  273. /package/examples/feature-demos/{theme-presets → theme-preset-documentation}/site/public/robots.txt +0 -0
  274. /package/examples/feature-demos/{theme-presets/site/routes/020-documentation → theme-preset-documentation/site}/theme.yaml +0 -0
  275. /package/examples/feature-demos/{theme-presets/site/routes/010-portfolio/images/portfolio → theme-preset-portfolio/site/pages/000-home/images}/puppy-portrait.jpg +0 -0
  276. /package/examples/feature-demos/{theme-presets/site/routes/010-portfolio/images/portfolio → theme-preset-portfolio/site/pages/000-home/images}/shepherd-portrait.jpg +0 -0
  277. /package/examples/feature-demos/{theme-presets/site/routes/010-portfolio/images/portfolio → theme-preset-portfolio/site/pages/000-home/images}/white-puppy.jpg +0 -0
  278. /package/examples/feature-demos/{theme-presets/site/routes/010-portfolio → theme-preset-portfolio/site}/theme.yaml +0 -0
  279. /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/collections/routeTheme.schema.json +0 -0
  280. /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/collections/site.schema.json +0 -0
  281. /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/collections/sitewide.schema.json +0 -0
  282. /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/collections/theme.schema.json +0 -0
  283. /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/content-assets.mjs +0 -0
  284. /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/content-modules.mjs +0 -0
  285. /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/content.d.ts +0 -0
  286. /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/data-store.json +0 -0
  287. /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/dev-local.json +0 -0
  288. /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/dev.json +0 -0
  289. /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/dev.log +0 -0
  290. /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/settings.json +0 -0
  291. /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/types.d.ts +0 -0
  292. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/_metadata.json +0 -0
  293. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro-D_GIpJIE.js +0 -0
  294. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro-D_GIpJIE.js.map +0 -0
  295. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro_n_aria-query.js +0 -0
  296. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro_n_aria-query.js.map +0 -0
  297. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro_n_axobject-query.js +0 -0
  298. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro_n_axobject-query.js.map +0 -0
  299. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro_n_html-escaper.js +0 -0
  300. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro_n_html-escaper.js.map +0 -0
  301. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +0 -0
  302. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +0 -0
  303. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/audit-DM9Hkl5B.js +0 -0
  304. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/audit-DM9Hkl5B.js.map +0 -0
  305. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/embla-carousel.js +0 -0
  306. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/embla-carousel.js.map +0 -0
  307. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/highlight-DnZ1ULOL.js +0 -0
  308. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/highlight-DnZ1ULOL.js.map +0 -0
  309. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/icons-RyebKi45.js +0 -0
  310. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/icons-RyebKi45.js.map +0 -0
  311. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/package.json +0 -0
  312. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +0 -0
  313. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/settings-DaU-gNnK.js +0 -0
  314. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/settings-DaU-gNnK.js.map +0 -0
  315. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/toolbar-63PA619H.js +0 -0
  316. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/toolbar-63PA619H.js.map +0 -0
  317. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/ui-library--bkcNSYz.js +0 -0
  318. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/ui-library--bkcNSYz.js.map +0 -0
  319. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/window-jPkpKVcy.js +0 -0
  320. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/window-jPkpKVcy.js.map +0 -0
  321. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/xray-BXCHpEJP.js +0 -0
  322. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/xray-BXCHpEJP.js.map +0 -0
  323. /package/examples/feature-demos/{theme-presets/site/routes/030-project/images/project → theme-preset-project/site/pages/000-home/images}/boxer-portrait.jpg +0 -0
  324. /package/examples/feature-demos/{theme-presets/site/routes/030-project/images/project → theme-preset-project/site/pages/000-home/images}/grass-puppy.jpg +0 -0
  325. /package/examples/feature-demos/{theme-presets/site/routes/030-project/images/project → theme-preset-project/site/pages/000-home/images}/grey-street-dog.jpg +0 -0
  326. /package/examples/feature-demos/{theme-presets/site/routes/030-project → theme-preset-project/site}/theme.yaml +0 -0
  327. /package/examples/feature-demos/{theme-presets/site/routes/040-statement/images/statement → theme-preset-statement/site/pages/000-home/images}/black-pedigree.jpg +0 -0
  328. /package/examples/feature-demos/{theme-presets/site/routes/040-statement/images/statement → theme-preset-statement/site/pages/000-home/images}/sheepdog-grass.jpg +0 -0
  329. /package/examples/feature-demos/{theme-presets/site/routes/040-statement/images/statement → theme-preset-statement/site/pages/000-home/images}/sheepdog-wide.jpg +0 -0
  330. /package/examples/feature-demos/{theme-presets/site/routes/040-statement → theme-preset-statement/site}/theme.yaml +0 -0
  331. /package/fixtures/basic/site/{images/work → pages/000-home/images}/.gitkeep +0 -0
  332. /package/fixtures/content-model-v2/site/{images/card-section → pages/000-home/images}/adopt.svg +0 -0
  333. /package/fixtures/content-model-v2/site/{images/intro → pages/000-home/images}/detail.jpg +0 -0
  334. /package/fixtures/content-model-v2/site/{images/intro → pages/000-home/images}/duplicate.jpg +0 -0
  335. /package/fixtures/content-model-v2/site/{images/card-section → pages/000-home/images}/foster.svg +0 -0
  336. /package/fixtures/content-model-v2/site/{images/intro → pages/000-home/images}/hero.jpg +0 -0
  337. /package/fixtures/content-model-v2/site/{images/plain → pages/000-home/images}/image.jpg +0 -0
  338. /package/fixtures/content-model-v2/site/{images/timed → pages/000-home/images}/slide-one.jpg +0 -0
  339. /package/fixtures/content-model-v2/site/{images/timed → pages/000-home/images}/slide-three.jpg +0 -0
  340. /package/fixtures/content-model-v2/site/{images/timed → pages/000-home/images}/slide-two.jpg +0 -0
  341. /package/fixtures/content-model-v2/site/{routes/010-guide/images/intro → pages/010-guide/images}/duplicate.jpg +0 -0
  342. /package/starters/basic/site/{images/work → pages/000-home/images}/.gitkeep +0 -0
@@ -0,0 +1,102 @@
1
+ import { homePageDirectory } from './site-conventions.mjs';
2
+
3
+ export const pageDirectoryPattern = /^(\d{3})-([a-z0-9]+(?:-[a-z0-9]+)*)$/;
4
+
5
+ const pageEntryPathSeparator = '--';
6
+
7
+ export const parsePageDirectory = (pageDirectory, label = 'page directory') => {
8
+ const match = pageDirectory.match(pageDirectoryPattern);
9
+
10
+ if (!match) {
11
+ throw new Error(
12
+ `Invalid ${label} "${pageDirectory}". Page directories must use the form NNN-page-id, for example 010-getting-started. Page ids may contain only lowercase letters, numbers, and single hyphens.`,
13
+ );
14
+ }
15
+ if (match[1] === '000' && pageDirectory !== homePageDirectory) {
16
+ throw new Error(`Invalid ${label} "${pageDirectory}". The 000 prefix is reserved for ${homePageDirectory}.`);
17
+ }
18
+
19
+ return {
20
+ pageDirectory,
21
+ pageOrder: Number.parseInt(match[1], 10),
22
+ pageId: match[2],
23
+ };
24
+ };
25
+
26
+ const normalizePageDirectoryPath = (pageDirectoryPath) => pageDirectoryPath.replaceAll('\\', '/');
27
+
28
+ export const parsePageDirectoryPath = (pageDirectoryPath, label = 'page directory path') => {
29
+ const normalizedPath = normalizePageDirectoryPath(pageDirectoryPath);
30
+ const segments = normalizedPath.split('/').filter(Boolean);
31
+
32
+ if (!normalizedPath || normalizedPath.startsWith('/') || normalizedPath.endsWith('/') || segments.length % 2 === 0) {
33
+ throw new Error(
34
+ `Invalid ${label} "${pageDirectoryPath}". Nested pages must use the form NNN-page-id/pages/NNN-child-id.`,
35
+ );
36
+ }
37
+
38
+ const pageDirectories = [];
39
+ for (const [index, segment] of segments.entries()) {
40
+ if (index % 2 === 1) {
41
+ if (segment !== 'pages') {
42
+ throw new Error(
43
+ `Invalid ${label} "${pageDirectoryPath}". Use a pages directory between nested page directories.`,
44
+ );
45
+ }
46
+ continue;
47
+ }
48
+
49
+ pageDirectories.push(parsePageDirectory(segment, `${label} segment ${segment}`));
50
+ }
51
+
52
+ const pageIds = pageDirectories.map(({ pageId }) => pageId);
53
+ const pageOrders = pageDirectories.map(({ pageOrder }) => pageOrder);
54
+ const currentPage = pageDirectories.at(-1);
55
+ const homeIndex = pageDirectories.findIndex(({ pageDirectory }) => pageDirectory === homePageDirectory);
56
+ if (homeIndex > 0) {
57
+ throw new Error(`Invalid ${label} "${pageDirectoryPath}". ${homePageDirectory} is allowed only as a top-level page.`);
58
+ }
59
+ if (homeIndex === 0 && pageDirectories.length > 1) {
60
+ throw new Error(
61
+ `Invalid ${label} "${pageDirectoryPath}". ${homePageDirectory} is the homepage and cannot contain child pages. Place the page beside it under site/pages/, or below another non-home page.`,
62
+ );
63
+ }
64
+ const isHome = homeIndex === 0;
65
+ const logicalPageIds = isHome ? [] : pageIds;
66
+ const logicalPageOrders = isHome ? [] : pageOrders;
67
+
68
+ return {
69
+ pageDirectory: normalizedPath,
70
+ pageDirectories: pageDirectories.map(({ pageDirectory }) => pageDirectory),
71
+ pageId: currentPage.pageId,
72
+ pageIds: logicalPageIds,
73
+ pageOrder: currentPage.pageOrder,
74
+ pageOrders: logicalPageOrders,
75
+ pagePath: logicalPageIds.join('/'),
76
+ parentPagePath: logicalPageIds.length > 1
77
+ ? logicalPageIds.slice(0, -1).join('/')
78
+ : null,
79
+ depth: pageDirectories.length,
80
+ };
81
+ };
82
+
83
+ export const encodePageDirectoryPath = (pageDirectoryPath) => {
84
+ const { pageDirectories } = parsePageDirectoryPath(pageDirectoryPath);
85
+ return pageDirectories.join(pageEntryPathSeparator);
86
+ };
87
+
88
+ export const decodePageDirectoryPath = (pageEntryPath, label = 'page entry path') => {
89
+ const pageDirectories = pageEntryPath.split(pageEntryPathSeparator);
90
+ if (pageDirectories.some((pageDirectory) => !pageDirectory)) {
91
+ throw new Error(`Invalid ${label} "${pageEntryPath}".`);
92
+ }
93
+
94
+ return pageDirectories.join('/pages/');
95
+ };
96
+
97
+ export const getPageDirectoryAncestors = (pageDirectoryPath) => {
98
+ const { pageDirectories } = parsePageDirectoryPath(pageDirectoryPath);
99
+ return pageDirectories.map((_, index) => (
100
+ pageDirectories.slice(0, index + 1).join('/pages/')
101
+ ));
102
+ };
@@ -5,7 +5,11 @@ import {
5
5
  import { resolveThemeConfig } from './theme-presets.mjs';
6
6
 
7
7
  export const presentationPaletteNames = ['dark', 'light', 'paper'];
8
- const sectionSurfaceNames = ['base', 'soft', 'emphasis'];
8
+ const sectionBackgroundPatterns = Object.freeze({
9
+ uniform: ['base'],
10
+ alternating: ['base', 'soft'],
11
+ cycling: ['base', 'soft', 'emphasis'],
12
+ });
9
13
 
10
14
  const presentationPalettes = Object.freeze({
11
15
  dark: {
@@ -92,34 +96,30 @@ export const getPresentationCssVariables = (presentation) => {
92
96
  };
93
97
  };
94
98
 
95
- const getSectionSurfaces = (value = ['base'], sourceLabel = 'theme.yaml') => {
96
- if (!Array.isArray(value) || value.length < 1 || value.length > 3) {
97
- throw new Error(`sectionSurfaces must contain one to three values in ${sourceLabel}.`);
98
- }
99
-
100
- for (const surface of value) {
101
- if (!sectionSurfaceNames.includes(surface)) {
102
- throw new Error(`Unknown section surface "${surface}" in ${sourceLabel}. Use one of: ${sectionSurfaceNames.join(', ')}.`);
103
- }
104
- }
105
-
106
- if (new Set(value).size !== value.length) {
107
- throw new Error(`Each section surface may appear only once in ${sourceLabel}.`);
99
+ const getSectionSurfaces = (pattern = 'uniform', sourceLabel = 'theme.yaml') => {
100
+ const surfaces = sectionBackgroundPatterns[pattern];
101
+ if (!surfaces) {
102
+ throw new Error(`sections.backgroundPattern must be one of ${Object.keys(sectionBackgroundPatterns).join(', ')} in ${sourceLabel}.`);
108
103
  }
109
104
 
110
- return value;
105
+ return surfaces;
111
106
  };
112
107
 
113
108
  export const resolveThemePresentation = (theme, sourceLabel = 'theme.yaml') => {
114
109
  const normalizedTheme = resolveThemeConfig(theme, sourceLabel);
115
110
  const paletteName = normalizedTheme.palette ?? 'dark';
116
111
  const palette = getPresentationPalette(paletteName);
112
+ const typography = resolveTypographyConfig(normalizedTheme.typography ?? defaultTypography);
113
+ const textWidth = normalizedTheme.layout?.textWidth;
114
+ if (textWidth !== undefined) {
115
+ typography.values.body.width = textWidth;
116
+ }
117
117
 
118
118
  return {
119
119
  paletteName,
120
120
  palette,
121
- sectionSurfaces: getSectionSurfaces(normalizedTheme.sectionSurfaces, sourceLabel),
122
- typography: resolveTypographyConfig(normalizedTheme.typography ?? defaultTypography),
121
+ sectionSurfaces: getSectionSurfaces(normalizedTheme.sections?.backgroundPattern, sourceLabel),
122
+ typography,
123
123
  };
124
124
  };
125
125
 
@@ -129,6 +129,14 @@ export const resolvePagePresentation = (theme, sourceLabel) => {
129
129
  return resolvedThemePresentation;
130
130
  };
131
131
 
132
+ const textWidthCssValues = Object.freeze({
133
+ narrow: 'min(60ch, var(--text-width))',
134
+ normal: 'min(72ch, var(--text-width))',
135
+ wide: 'min(72ch, var(--image-layout-width))',
136
+ });
137
+
138
+ export const getTextWidthCssValue = (textWidth) => textWidthCssValues[textWidth];
139
+
132
140
  export const resolveSectionSurface = (pagePresentation, sectionIndex) => {
133
141
  const surfaceName = pagePresentation.sectionSurfaces[sectionIndex % pagePresentation.sectionSurfaces.length];
134
142
 
@@ -2,6 +2,7 @@ import { readFile } from 'node:fs/promises';
2
2
  import {
3
3
  validateConfigYamlStructure,
4
4
  } from './site-content.mjs';
5
+ import { navigationModeNames } from './navigation-model.mjs';
5
6
  import {
6
7
  siteConfigLabel,
7
8
  siteConfigPath,
@@ -182,19 +183,27 @@ const readSiteUrl = (config) => {
182
183
 
183
184
  const localeLabels = Object.freeze({
184
185
  en: Object.freeze({
186
+ breadcrumb: 'Breadcrumb',
185
187
  dismissBanner: 'Dismiss notice',
188
+ built: 'Built',
186
189
  images: 'Images',
190
+ navigationChildren: 'Child pages',
191
+ navigationMenu: 'Menu',
187
192
  note: 'Note',
188
- pageNavigation: 'On this page',
193
+ pageNavigation: 'Page contents',
189
194
  siteBanners: 'Site notices',
190
195
  siteNavigation: 'Pages',
191
196
  skipToContent: 'Skip to content',
192
197
  }),
193
198
  sv: Object.freeze({
199
+ breadcrumb: 'Brödsmulor',
194
200
  dismissBanner: 'Stäng meddelande',
201
+ built: 'Byggd',
195
202
  images: 'Bilder',
203
+ navigationChildren: 'Undersidor',
204
+ navigationMenu: 'Meny',
196
205
  note: 'Not',
197
- pageNavigation: 'På den här sidan',
206
+ pageNavigation: 'Sidinnehåll',
198
207
  siteBanners: 'Meddelanden',
199
208
  siteNavigation: 'Sidor',
200
209
  skipToContent: 'Hoppa till innehållet',
@@ -228,8 +237,8 @@ const siteUrl = readSiteUrl(rawConfig);
228
237
  const scrollBehaviorNames = ['instant', 'smooth'];
229
238
 
230
239
  const defaultFontFamily = "Arial, 'Helvetica Neue', Helvetica, sans-serif";
231
- const layoutDensityNames = ['compact', 'normal', 'airy'];
232
- const layoutDensityProfiles = Object.freeze({
240
+ const contentSpacingNames = ['compact', 'normal', 'spacious'];
241
+ const contentSpacingProfiles = Object.freeze({
233
242
  compact: Object.freeze({
234
243
  blockGap: Object.freeze({
235
244
  desktop: '1.25em',
@@ -282,7 +291,7 @@ const layoutDensityProfiles = Object.freeze({
282
291
  mobile: '1.5rem',
283
292
  }),
284
293
  }),
285
- airy: Object.freeze({
294
+ spacious: Object.freeze({
286
295
  blockGap: Object.freeze({
287
296
  desktop: '1.75em',
288
297
  mobile: '1.5em',
@@ -315,12 +324,19 @@ export const resolveThemeVisualConfig = (theme, sourceLabel = siteThemeLabel) =>
315
324
  const rawLayoutSpacingConfig = assertObject(rawLayoutConfig.spacing ?? {}, 'layout.spacing', sourceLabel);
316
325
  const rawImagesConfig = assertObject(rawThemeConfig.images ?? {}, 'images', sourceLabel);
317
326
  const rawTypographyConfig = assertObject(rawThemeConfig.typography ?? {}, 'typography', sourceLabel);
318
- const resolvedLayoutDensity = readEnum(rawLayoutConfig, 'density', 'layout', layoutDensityNames, 'normal', sourceLabel);
319
- const resolvedLayoutSpacingDefaults = layoutDensityProfiles[resolvedLayoutDensity];
327
+ const resolvedContentSpacing = readEnum(rawLayoutConfig, 'contentSpacing', 'layout', contentSpacingNames, 'normal', sourceLabel);
328
+ const resolvedLayoutSpacingDefaults = contentSpacingProfiles[resolvedContentSpacing];
329
+ const shape = readEnum(rawThemeConfig, 'shape', 'theme', ['square', 'soft'], 'soft', sourceLabel);
320
330
 
321
331
  return Object.freeze({
322
332
  layout: Object.freeze({
323
- density: resolvedLayoutDensity,
333
+ contentSpacing: resolvedContentSpacing,
334
+ textWidth: readEnum(rawLayoutConfig, 'textWidth', 'layout', ['narrow', 'normal', 'wide'], 'normal', sourceLabel),
335
+ localNavigationGap: readCssLengthValue(
336
+ rawLayoutConfig.localNavigationGap ?? 'clamp(1.5rem, 3vw, 3rem)',
337
+ 'layout.localNavigationGap',
338
+ sourceLabel,
339
+ ),
324
340
  gutter: readResponsiveCssLength(rawLayoutConfig, 'gutter', 'layout', Object.freeze({
325
341
  desktop: 'clamp(1.25rem, 4vw, 3rem)',
326
342
  mobile: '1rem',
@@ -349,6 +365,27 @@ export const resolveThemeVisualConfig = (theme, sourceLabel = siteThemeLabel) =>
349
365
  typography: Object.freeze({
350
366
  fontFamily: readFontFamily(rawTypographyConfig, 'fontFamily', 'typography', defaultFontFamily, sourceLabel),
351
367
  }),
368
+ shape: Object.freeze({
369
+ name: shape,
370
+ radiusSmall: shape === 'square' ? '0' : '2px',
371
+ radiusMedium: shape === 'square' ? '0' : '6px',
372
+ radiusLarge: shape === 'square' ? '0' : '8px',
373
+ }),
374
+ });
375
+ };
376
+
377
+ export const resolveNavigationConfig = (config, sourceLabel = siteConfigLabel) => {
378
+ const rawNavigation = assertObject(config.navigation ?? {}, 'navigation', sourceLabel);
379
+
380
+ return Object.freeze({
381
+ mode: readEnum(
382
+ rawNavigation,
383
+ 'mode',
384
+ 'navigation',
385
+ navigationModeNames,
386
+ 'automatic',
387
+ sourceLabel,
388
+ ),
352
389
  });
353
390
  };
354
391
 
@@ -359,6 +396,7 @@ export const projectConfig = Object.freeze({
359
396
  }),
360
397
  ...resolveThemeVisualConfig(rawTheme, siteThemeLabel),
361
398
  navigation: Object.freeze({
399
+ ...resolveNavigationConfig(rawConfig, siteConfigLabel),
362
400
  scrollBehavior: readEnum(
363
401
  rawConfig,
364
402
  'scrollBehavior',
@@ -1,4 +1,5 @@
1
1
  import { z } from 'astro/zod';
2
+ import { navigationModeNames } from './navigation-model.mjs';
2
3
  import { themePresetNames } from './theme-presets.mjs';
3
4
 
4
5
  const isDateOnly = (value) => {
@@ -10,13 +11,16 @@ const isDateOnly = (value) => {
10
11
 
11
12
  const textAlign = z.enum(['left', 'center', 'right']).describe('Text alignment. Omit an override to keep the active typography profile.');
12
13
  const textSize = z.enum(['small', 'medium', 'large', 'xlarge']).describe('Text size from the active typography system. Omit an override to keep the profile value.');
13
- const textWidth = z.enum(['narrow', 'normal', 'wide']).describe('Maximum width of body text. Omit an override to keep the profile value.');
14
+ const textWidth = z.enum(['narrow', 'normal', 'wide']).describe('Maximum width of body text. Omit this to keep the inherited page setting.');
14
15
  const headingWeight = z.union([z.literal(400), z.literal(500), z.literal(600), z.literal(700)]).describe('CSS font weight.');
15
16
  const typographyProfile = z.enum(['restrained', 'dense', 'reading', 'statement']).describe('Coordinated typography defaults. Omit this to use the selected preset.');
16
17
  const themePreset = z.enum(themePresetNames).describe('Complete Norna visual preset. Start here and add overrides only when needed.');
17
18
  const presentationPalette = z.enum(['dark', 'light', 'paper']).describe('Coordinated site color palette. Omit this to use the selected preset.');
18
- const sectionSurface = z.enum(['base', 'soft', 'emphasis']).describe('Semantic section surface from the active palette.');
19
19
  const spacingDensity = z.enum(['compact', 'normal', 'airy']).describe('Coordinated spacing density. Omit this to use the selected preset.');
20
+ const contentSpacing = z.enum(['compact', 'normal', 'spacious']).describe('Vertical spacing between page sections and structured content blocks.');
21
+ const backgroundPattern = z.enum(['uniform', 'alternating', 'cycling']).describe('How sections cycle through the active palette surfaces.');
22
+ const shapeProfile = z.enum(['square', 'soft']).describe('Site-wide corner treatment for interface and content surfaces.');
23
+ const navigationMode = z.enum(navigationModeNames).describe('Site-wide navigation model. Automatic selects from the site structure.');
20
24
  const lineHeight = z.number()
21
25
  .min(1, 'Use a unitless line height of at least 1.')
22
26
  .max(3, 'Use a unitless line height of at most 3.')
@@ -71,7 +75,6 @@ const headingLevelsPresentationOverride = z.object({
71
75
  }).strict().describe('Heading overrides by Markdown level.');
72
76
  const bodyPresentationOverride = z.object({
73
77
  ...commonTextPresentationOverride,
74
- width: textWidth.optional().describe('Maximum body-text line length.'),
75
78
  paragraphSpacing: cssLength.optional().describe('Vertical space between body paragraphs.'),
76
79
  }).strict().describe('Focused overrides for body text.');
77
80
  const captionPresentationOverride = z.object({
@@ -119,95 +122,115 @@ const themeLayoutSpacing = z.object({
119
122
  sectionGap: responsiveCssLength.optional().describe('Vertical separation between page sections.'),
120
123
  }).strict().describe('Fine-grained spacing overrides applied after layout density.');
121
124
  const themeLayout = z.object({
122
- density: spacingDensity.optional().describe('Overall spacing density. Omit this to keep the selected preset.'),
125
+ contentSpacing: contentSpacing.optional().describe('Page content spacing. Omit this to keep the selected preset or inherited page setting.'),
126
+ textWidth: textWidth.optional().describe('Body-text line length. Omit this to keep the selected preset or inherited page setting.'),
123
127
  pageWidth: visualCssLength.optional().describe('Maximum width of the site layout. Omit this to keep the selected preset.'),
124
128
  gutter: responsiveCssLength.optional().describe('Horizontal page gutter as one value or separate desktop and mobile values.'),
125
129
  spacing: themeLayoutSpacing.optional().describe('Fine-grained spacing overrides.'),
126
130
  }).strict().describe('Optional layout overrides applied after the preset.');
131
+ const pageThemeLayout = z.object({
132
+ contentSpacing: contentSpacing.optional().describe('Page content spacing. Descendant pages inherit this value.'),
133
+ textWidth: textWidth.optional().describe('Body-text line length. Descendant pages inherit this value.'),
134
+ }).strict().refine(
135
+ (value) => value.contentSpacing !== undefined || value.textWidth !== undefined,
136
+ 'Specify contentSpacing, textWidth, or both.',
137
+ ).describe('Page-local layout settings inherited by descendant pages.');
127
138
  const themeImages = z.object({
128
139
  width: visualCssLength.optional().describe('Maximum managed-image width. Omit this to keep the selected preset.'),
129
140
  maxAvailableWidthPercent: responsivePercent.optional().describe('Maximum percentage of available horizontal space used by managed images.'),
130
141
  maxAvailableHeightPercent: responsivePercent.optional().describe('Maximum percentage of viewport height used by managed images.'),
131
142
  }).strict().describe('Optional defaults for managed image presentation.');
132
- const sectionSurfaces = z.array(sectionSurface).min(1).max(3).refine(
133
- (value) => new Set(value).size === value.length,
134
- 'Each section surface may appear only once.',
135
- ).describe('Surface sequence cycled across page sections.');
143
+ const configNavigation = z.object({
144
+ mode: navigationMode.optional().describe('Navigation model. Omit this to let Norna choose from the site structure.'),
145
+ }).strict().describe('Site-wide navigation behavior.');
146
+ const themeSections = z.object({
147
+ backgroundPattern: backgroundPattern.optional().describe('Section background sequence. Omit this to keep the selected preset or inherited page setting.'),
148
+ }).strict().describe('Defaults for page section presentation.');
149
+ const pageThemeSections = z.object({
150
+ backgroundPattern: backgroundPattern.optional().describe('Section background sequence inherited by descendant pages.'),
151
+ }).strict().refine(
152
+ (value) => value.backgroundPattern !== undefined,
153
+ 'Specify backgroundPattern.',
154
+ ).describe('Page-local section presentation inherited by descendant pages.');
136
155
  const pageNavigation = z.object({
137
- include: z.boolean().optional().describe('Include this page in site navigation. Pages are included when omitted.'),
138
- label: z.string().optional().describe('Navigation label when it should differ from the page title.'),
156
+ listed: z.boolean().optional().default(true).describe('List this page in site navigation. The page remains public when false.'),
139
157
  }).strict();
140
- const sitewideNavigation = z.object({
141
- label: z.string().min(1).optional().describe('Accessible site label used for navigation text and logo alternative text.'),
142
- logo: z.object({
143
- height: visualCssLength.optional().describe('Displayed logo height. Width follows the intrinsic aspect ratio.'),
144
- }).strict().optional().describe('Optional display settings for a convention-based navigation logo.'),
145
- }).strict().describe('Site-wide identity shown in navigation.');
158
+ const pageMetadata = z.object({
159
+ description: z.string().min(1).optional().describe('Optional page-specific meta description.'),
160
+ }).strict().describe('Optional metadata for this homepage or additional page. The Markdown H1 supplies the page title.');
161
+ const sitewideLogo = z.object({
162
+ height: visualCssLength.optional().describe('Displayed logo height. Width follows the intrinsic aspect ratio.'),
163
+ }).strict().describe('Optional display settings for a convention-based navigation logo.');
146
164
 
147
- const sectionMetadata = z.object({
148
- visible: visibilityWindow.optional().describe('Optional date window for this section.'),
149
- }).strict().describe('Metadata keyed by the explicit Markdown section id. Markdown order remains authoritative.');
150
165
  const banner = z.object({
151
166
  id: z.string().regex(/^[a-z0-9-]+$/).describe('Stable banner identifier used for dismissal state.'),
152
167
  tone: z.enum(['warning']).default('warning').describe('Semantic banner tone.'),
153
168
  visible: visibilityWindow.optional().describe('Optional date window for the banner.'),
154
169
  title: z.string().min(1).describe('Short banner heading.'),
155
170
  text: z.string().min(1).describe('Concise banner message.'),
156
- }).strict().describe('Dismissible, site-wide one-line notice.');
171
+ }).strict().describe('Dismissible site-wide warning shown above page content for important temporary information.');
157
172
  const banners = z.array(banner).optional().default([]).refine(
158
173
  (values) => new Set(values.map((value) => value.id)).size === values.length,
159
174
  'Banner ids must be unique.',
160
175
  );
161
- const dateTimeFormat = z.object({
162
- locale: z.string().min(1).describe('Intl locale such as en-GB or sv-SE.'),
163
- timeZone: z.string().min(1).describe('IANA time zone such as Europe/Stockholm.'),
164
- dateStyle: z.enum(['short', 'medium', 'long', 'full']).describe('Intl dateStyle for the build timestamp.'),
165
- timeStyle: z.enum(['short', 'medium', 'long', 'full']).describe('Intl timeStyle for the build timestamp.'),
166
- }).strict().refine((value) => {
167
- try {
168
- new Intl.DateTimeFormat(value.locale, {
169
- dateStyle: value.dateStyle,
170
- timeStyle: value.timeStyle,
171
- timeZone: value.timeZone,
172
- });
173
- return true;
174
- } catch {
175
- return false;
176
- }
177
- }, 'Use a valid Intl.DateTimeFormat configuration.');
178
176
  const sitewideFooter = z.object({
179
177
  copyrightMessage: z.string().min(1).optional().describe('Copyright or ownership text shown in the site footer.'),
180
- buildInfo: z.object({
181
- enabled: z.boolean().default(true).describe('Show generated build information in the footer.'),
182
- text: z.string().min(1).describe('Label shown before the generated build timestamp.'),
183
- dateTimeFormat: dateTimeFormat.describe('Formatting used for the generated build timestamp.'),
184
- }).strict().optional().describe('Optional generated build timestamp.'),
178
+ buildInfo: z.boolean().optional().default(false).describe('Show a localized generated build timestamp in the footer.'),
185
179
  }).strict().describe('Site-wide footer content.');
186
180
 
187
- export const configSchema = z.object({
181
+ const configShape = {
188
182
  url: z.string().url().describe('Absolute public URL for the built site.'),
189
183
  language: z.string().regex(/^(?:en|sv)(?:-[a-zA-Z0-9]+)*$/).optional().describe('Site language tag using Norna\'s English or Swedish interface text; the default is en.'),
184
+ navigation: configNavigation.optional(),
190
185
  scrollBehavior: z.enum(['instant', 'smooth']).optional().default('instant').describe('Use instant anchors by default or the browser\'s native smooth scrolling.'),
191
- }).strict().describe('Technical settings for one Norna site. Routes cannot provide technical configuration.');
186
+ };
192
187
 
193
- export const siteSchema = z.object({
194
- title: z.string().describe('Page title used in metadata and route navigation.'),
195
- description: z.string().describe('Page description used in metadata.'),
188
+ const siteShape = {
189
+ page: pageMetadata.optional(),
196
190
  navigation: pageNavigation.optional().describe('Optional navigation metadata for this page.'),
197
- sections: z.record(z.string().regex(/^[a-z0-9-]+$/), sectionMetadata).optional().default({}).describe('Optional metadata keyed by Markdown section id. Do not duplicate section order here.'),
198
- }).strict().describe('Frontmatter for a homepage or route content.md file.');
191
+ };
199
192
 
200
- export const themeVisualSchema = z.object({
193
+ const themeVisualShape = {
201
194
  preset: themePreset.optional().describe('Complete visual starting point. Add only the overrides the site actually needs.'),
195
+ shape: shapeProfile.optional().describe('Site-wide shape profile. Omit this to use the selected preset.'),
202
196
  layout: themeLayout.optional(),
203
197
  images: themeImages.optional(),
204
198
  typography: themeTypography.optional(),
205
199
  palette: presentationPalette.optional(),
206
- sectionSurfaces: sectionSurfaces.optional().describe('Surface sequence cycled through page sections. Omit this to keep the selected preset.'),
207
- }).strict().describe('Visual settings for a site or route. A route theme replaces the inherited visual theme; site identity remains site-wide.');
200
+ sections: themeSections.optional(),
201
+ };
208
202
 
209
- export const sitewideSchema = z.object({
210
- navigation: sitewideNavigation.optional().describe('Site-wide navigation identity.'),
203
+ const pageThemeShape = {
204
+ layout: pageThemeLayout.optional(),
205
+ images: themeImages.optional(),
206
+ sections: pageThemeSections.optional(),
207
+ };
208
+
209
+ const sitewideShape = {
210
+ logo: sitewideLogo.optional(),
211
211
  banners: banners.describe('Site-wide dismissible notices.'),
212
212
  footer: sitewideFooter.optional().describe('Site-wide footer content.'),
213
- }).strict().describe('Editorial content and identity shared by every page.');
213
+ };
214
+
215
+ export const schemaTopLevelKeys = Object.freeze({
216
+ config: Object.freeze(Object.keys(configShape)),
217
+ content: Object.freeze(Object.keys(siteShape)),
218
+ sitewide: Object.freeze(Object.keys(sitewideShape)),
219
+ theme: Object.freeze(Object.keys(themeVisualShape)),
220
+ pageTheme: Object.freeze(Object.keys(pageThemeShape)),
221
+ });
222
+
223
+ export const configSchema = z.object(configShape).strict()
224
+ .describe('Technical settings for one Norna site. Additional pages cannot provide technical configuration.');
225
+ export const siteSchema = z.object(siteShape).strict()
226
+ .describe('Frontmatter for a homepage or additional page content.md file.');
227
+ export const themeVisualSchema = z.object(themeVisualShape).strict()
228
+ .describe('Site-wide visual identity and default page presentation. Presets may be selected only in the root theme.');
229
+ export const pageThemeSchema = z.object(pageThemeShape).strict()
230
+ .refine(
231
+ (value) => value.layout !== undefined || value.images !== undefined || value.sections !== undefined,
232
+ 'Specify layout, images, sections, or a combination of them.',
233
+ )
234
+ .describe('Limited page presentation overrides inherited by descendant pages. Site colors, shapes, typography and navigation remain global.');
235
+ export const sitewideSchema = z.object(sitewideShape).strict()
236
+ .describe('Editorial content and optional navigation logo display settings shared by every page.');