@janga/norna 0.7.9 → 0.7.11

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 (287) hide show
  1. package/README.md +4 -2
  2. package/bin/norna-cli.mjs +3 -0
  3. package/docs/README.md +16 -12
  4. package/docs/commands.md +6 -0
  5. package/docs/configuration.md +10 -54
  6. package/docs/content.md +20 -21
  7. package/docs/design/norna-diagram-design.md +2 -0
  8. package/docs/design/site-examples-structure.md +35 -24
  9. package/docs/engine-development.md +13 -7
  10. package/docs/getting-started.md +56 -19
  11. package/docs/routes.md +8 -6
  12. package/docs/site-structure.md +11 -6
  13. package/docs/sitewide-content.md +74 -0
  14. package/docs/theme.md +125 -140
  15. package/docs/typography.md +72 -45
  16. package/examples/README.md +41 -0
  17. package/examples/complete-sites/dog-shelter-multi-page/.astro/collections/site.schema.json +59 -0
  18. package/examples/complete-sites/dog-shelter-multi-page/.astro/collections/sitewide.schema.json +138 -0
  19. package/examples/complete-sites/dog-shelter-multi-page/.astro/collections/theme.schema.json +755 -0
  20. package/examples/complete-sites/dog-shelter-multi-page/.astro/content-modules.mjs +1 -0
  21. package/examples/{typography-presets → complete-sites/dog-shelter-multi-page}/.astro/content.d.ts +8 -0
  22. package/examples/complete-sites/dog-shelter-multi-page/.astro/data-store.json +1 -0
  23. package/examples/{typography-presets → complete-sites/dog-shelter-multi-page}/.astro/dev-local.json +2 -2
  24. package/examples/{typography-presets → complete-sites/dog-shelter-multi-page}/.astro/dev.json +2 -2
  25. package/examples/complete-sites/dog-shelter-multi-page/.astro/dev.log +53 -0
  26. package/examples/complete-sites/dog-shelter-multi-page/.astro/settings.json +5 -0
  27. package/examples/complete-sites/dog-shelter-multi-page/README.md +9 -0
  28. package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/_metadata.json +13 -7
  29. package/examples/complete-sites/dog-shelter-multi-page/site/.norna/generated-images.json +18 -0
  30. package/examples/complete-sites/dog-shelter-multi-page/site/config.mjs +30 -0
  31. package/examples/complete-sites/dog-shelter-multi-page/site/content.md +12 -0
  32. package/examples/complete-sites/dog-shelter-multi-page/site/public/favicon.svg +10 -0
  33. package/examples/complete-sites/dog-shelter-multi-page/site/routes/010-dogs/images/dogs/rex.svg +19 -0
  34. package/examples/complete-sites/dog-shelter-multi-page/site/routes/010-dogs/images/dogs/rover.svg +19 -0
  35. package/examples/complete-sites/dog-shelter-multi-page/site/routes/010-dogs/route-content.md +30 -0
  36. package/examples/complete-sites/dog-shelter-multi-page/site/routes/020-adopt/route-content.md +12 -0
  37. package/examples/complete-sites/dog-shelter-multi-page/site/sitewide-content.md +6 -0
  38. package/examples/complete-sites/dog-shelter-multi-page/site/theme.md +25 -0
  39. package/examples/complete-sites/dog-shelter-single-page/README.md +9 -0
  40. package/examples/complete-sites/dog-shelter-single-page/site/.norna/generated-images.json +18 -0
  41. package/examples/complete-sites/dog-shelter-single-page/site/config.mjs +30 -0
  42. package/examples/complete-sites/dog-shelter-single-page/site/content.md +38 -0
  43. package/examples/complete-sites/dog-shelter-single-page/site/images/what-we-do/dog-house.svg +25 -0
  44. package/examples/complete-sites/dog-shelter-single-page/site/images/you-can-help/heart.svg +13 -0
  45. package/examples/complete-sites/dog-shelter-single-page/site/public/favicon.svg +10 -0
  46. package/examples/complete-sites/dog-shelter-single-page/site/public/robots.txt +2 -0
  47. package/examples/complete-sites/dog-shelter-single-page/site/sitewide-content.md +6 -0
  48. package/examples/complete-sites/dog-shelter-single-page/site/theme.md +25 -0
  49. package/examples/feature-demos/media-and-surfaces/.astro/collections/site.schema.json +59 -0
  50. package/examples/feature-demos/media-and-surfaces/.astro/collections/sitewide.schema.json +138 -0
  51. package/examples/feature-demos/media-and-surfaces/.astro/collections/theme.schema.json +755 -0
  52. package/examples/feature-demos/media-and-surfaces/.astro/content-modules.mjs +1 -0
  53. package/examples/feature-demos/media-and-surfaces/.astro/content.d.ts +183 -0
  54. package/examples/feature-demos/media-and-surfaces/.astro/data-store.json +1 -0
  55. package/examples/feature-demos/media-and-surfaces/.astro/dev.log +11 -0
  56. package/examples/feature-demos/media-and-surfaces/.astro/settings.json +5 -0
  57. package/examples/feature-demos/media-and-surfaces/.astro/types.d.ts +2 -0
  58. package/examples/feature-demos/media-and-surfaces/README.md +15 -0
  59. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/_metadata.json +74 -0
  60. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro-B3JHJyiv.js +434 -0
  61. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro-B3JHJyiv.js.map +1 -0
  62. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro_n_aria-query.js +6390 -0
  63. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro_n_aria-query.js.map +1 -0
  64. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro_n_axobject-query.js +2702 -0
  65. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro_n_axobject-query.js.map +1 -0
  66. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro_n_html-escaper.js +65 -0
  67. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro_n_html-escaper.js.map +1 -0
  68. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +455 -0
  69. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +1 -0
  70. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/audit-C3SVr7dT.js +1534 -0
  71. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/audit-C3SVr7dT.js.map +1 -0
  72. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/highlight-BLtvqtzO.js +48 -0
  73. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/highlight-BLtvqtzO.js.map +1 -0
  74. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/icons-C92i61-2.js +42 -0
  75. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/icons-C92i61-2.js.map +1 -0
  76. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/settings-v5GgKks_.js +190 -0
  77. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/settings-v5GgKks_.js.map +1 -0
  78. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/toolbar-BFvJVp26.js +458 -0
  79. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/toolbar-BFvJVp26.js.map +1 -0
  80. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/ui-library-Cd35oAvo.js +1016 -0
  81. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/ui-library-Cd35oAvo.js.map +1 -0
  82. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/window-vo3A6TMn.js +32 -0
  83. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/window-vo3A6TMn.js.map +1 -0
  84. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/xray-DwhzGckb.js +111 -0
  85. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/xray-DwhzGckb.js.map +1 -0
  86. package/examples/feature-demos/media-and-surfaces/site/.norna/generated-images.json +66 -0
  87. package/examples/feature-demos/media-and-surfaces/site/config.mjs +30 -0
  88. package/examples/feature-demos/media-and-surfaces/site/content.md +64 -0
  89. package/examples/feature-demos/media-and-surfaces/site/public/robots.txt +2 -0
  90. package/examples/feature-demos/media-and-surfaces/site/routes/010-media/images/cards/card-carousel.svg +11 -0
  91. package/examples/feature-demos/media-and-surfaces/site/routes/010-media/images/cards/card-stack.svg +9 -0
  92. package/examples/feature-demos/media-and-surfaces/site/routes/010-media/images/cards/card-surfaces.svg +8 -0
  93. package/examples/feature-demos/media-and-surfaces/site/routes/010-media/images/carousel/carousel-one.svg +8 -0
  94. package/examples/feature-demos/media-and-surfaces/site/routes/010-media/images/carousel/carousel-three.svg +13 -0
  95. package/examples/feature-demos/media-and-surfaces/site/routes/010-media/images/carousel/carousel-two.svg +8 -0
  96. package/examples/feature-demos/media-and-surfaces/site/routes/010-media/images/stack/stack-one.svg +8 -0
  97. package/examples/feature-demos/media-and-surfaces/site/routes/010-media/images/stack/stack-two.svg +9 -0
  98. package/examples/feature-demos/media-and-surfaces/site/routes/010-media/route-content.md +85 -0
  99. package/examples/feature-demos/media-and-surfaces/site/routes/010-media/theme.md +3 -0
  100. package/examples/feature-demos/media-and-surfaces/site/routes/020-surfaces/route-content.md +44 -0
  101. package/examples/feature-demos/media-and-surfaces/site/routes/020-surfaces/theme.md +8 -0
  102. package/examples/feature-demos/media-and-surfaces/site/sitewide-content.md +6 -0
  103. package/examples/feature-demos/media-and-surfaces/site/theme.md +3 -0
  104. package/examples/{typography-presets/.astro/collections/theme.schema.json → feature-demos/theme-presets/.astro/collections/routeTheme.schema.json} +143 -65
  105. package/examples/feature-demos/theme-presets/.astro/collections/site.schema.json +59 -0
  106. package/examples/feature-demos/theme-presets/.astro/collections/sitewide.schema.json +138 -0
  107. package/examples/feature-demos/theme-presets/.astro/collections/theme.schema.json +755 -0
  108. package/examples/feature-demos/theme-presets/.astro/content-assets.mjs +4 -0
  109. package/examples/feature-demos/theme-presets/.astro/content-modules.mjs +1 -0
  110. package/examples/feature-demos/theme-presets/.astro/content.d.ts +183 -0
  111. package/examples/feature-demos/theme-presets/.astro/data-store.json +1 -0
  112. package/examples/feature-demos/theme-presets/.astro/dev-local.json +8 -0
  113. package/examples/feature-demos/theme-presets/.astro/dev.json +15 -0
  114. package/examples/feature-demos/theme-presets/.astro/dev.log +127 -0
  115. package/examples/feature-demos/theme-presets/.astro/types.d.ts +2 -0
  116. package/examples/feature-demos/theme-presets/README.md +14 -0
  117. package/examples/feature-demos/theme-presets/node_modules/.vite/deps/embla-carousel.js +1389 -0
  118. package/examples/feature-demos/theme-presets/node_modules/.vite/deps/embla-carousel.js.map +1 -0
  119. package/examples/feature-demos/theme-presets/node_modules/.vite/deps/package.json +3 -0
  120. package/examples/feature-demos/theme-presets/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +4 -0
  121. package/examples/feature-demos/theme-presets/site/.norna/generated-images.json +290 -0
  122. package/examples/{typography-presets → feature-demos/theme-presets}/site/config.mjs +4 -16
  123. package/examples/feature-demos/theme-presets/site/content.md +17 -0
  124. package/examples/feature-demos/theme-presets/site/routes/010-portfolio/route-content.md +34 -0
  125. package/examples/feature-demos/theme-presets/site/routes/010-portfolio/theme.md +3 -0
  126. package/examples/feature-demos/theme-presets/site/routes/020-documentation/route-content.md +33 -0
  127. package/examples/feature-demos/theme-presets/site/routes/020-documentation/theme.md +3 -0
  128. package/examples/feature-demos/theme-presets/site/routes/030-project/route-content.md +34 -0
  129. package/examples/feature-demos/theme-presets/site/routes/030-project/theme.md +3 -0
  130. package/examples/feature-demos/theme-presets/site/routes/040-statement/route-content.md +34 -0
  131. package/examples/feature-demos/theme-presets/site/routes/040-statement/theme.md +3 -0
  132. package/examples/feature-demos/theme-presets/site/sitewide-content.md +14 -0
  133. package/examples/feature-demos/theme-presets/site/theme.md +3 -0
  134. package/fixtures/basic/site/config.mjs +0 -13
  135. package/fixtures/basic/site/sitewide-content.md +12 -0
  136. package/fixtures/basic/site/theme.md +2 -2
  137. package/fixtures/content-model-v2/site/content.md +2 -5
  138. package/fixtures/content-model-v2/site/theme.md +1 -3
  139. package/package.json +9 -4
  140. package/scripts/build-pages.mjs +71 -0
  141. package/scripts/check-config.mjs +23 -1
  142. package/scripts/export-theme-preset.mjs +31 -0
  143. package/scripts/init-site.mjs +1 -0
  144. package/scripts/lib/example-sites.mjs +34 -0
  145. package/scripts/lib/frontmatter-yaml.mjs +28 -0
  146. package/scripts/lib/logo-assets.mjs +35 -0
  147. package/scripts/lib/norna-markdown-blocks.mjs +166 -0
  148. package/scripts/lib/presentation.mjs +106 -47
  149. package/scripts/lib/project-config.mjs +68 -105
  150. package/scripts/lib/site-content.mjs +26 -118
  151. package/scripts/lib/site-paths.mjs +2 -0
  152. package/scripts/lib/sitewide-config.mjs +34 -0
  153. package/scripts/lib/theme-config.mjs +75 -8
  154. package/scripts/lib/theme-presets.mjs +209 -0
  155. package/scripts/lib/typography.mjs +20 -26
  156. package/scripts/show-typography.mjs +43 -149
  157. package/scripts/sync-content-sections.mjs +19 -17
  158. package/scripts/test-content-check.mjs +59 -27
  159. package/scripts/test-content-model-v2.mjs +154 -0
  160. package/scripts/test-engine-commands.mjs +4 -1
  161. package/scripts/test-examples.mjs +15 -0
  162. package/scripts/test-navigation.mjs +73 -45
  163. package/scripts/test-package-check.mjs +66 -24
  164. package/scripts/test-temporary-visibility.mjs +19 -2
  165. package/scripts/test-theme-presets.mjs +151 -0
  166. package/src/components/CardList.astro +5 -2
  167. package/src/components/GalleryGrid.astro +37 -16
  168. package/src/components/SiteBanners.astro +100 -0
  169. package/src/components/SiteNavigation.astro +48 -31
  170. package/src/components/SitePage.astro +55 -13
  171. package/src/components/SiteSection.astro +193 -35
  172. package/src/content.config.ts +80 -48
  173. package/src/layouts/BaseLayout.astro +64 -14
  174. package/src/lib/routeThemes.ts +61 -0
  175. package/src/lib/sectionContent.ts +235 -51
  176. package/src/lib/sitePublicAssets.ts +1 -0
  177. package/src/styles/global.css +284 -93
  178. package/starters/basic/.github/workflows/deploy.yml +1 -1
  179. package/starters/basic/README.md +7 -6
  180. package/starters/basic/package.json +1 -0
  181. package/starters/basic/site/config.mjs +0 -13
  182. package/starters/basic/site/content.md +1 -1
  183. package/starters/basic/site/sitewide-content.md +14 -0
  184. package/starters/basic/site/theme.md +13 -24
  185. package/starters/project/.github/workflows/deploy.yml +1 -1
  186. package/starters/project/README.md +10 -8
  187. package/starters/project/package.json +1 -0
  188. package/starters/project/site/config.mjs +0 -13
  189. package/starters/project/site/sitewide-content.md +14 -0
  190. package/starters/project/site/theme.md +3 -11
  191. package/examples/dog-gallery/site/.norna/generated-images.json +0 -226
  192. package/examples/dog-gallery/site/config.mjs +0 -67
  193. package/examples/dog-gallery/site/content.md +0 -128
  194. package/examples/dog-gallery/site/images/black-dogs/black-puppy-meadow.png +0 -0
  195. package/examples/dog-gallery/site/images/black-dogs/photo-of-a-black-dog.jpg +0 -0
  196. package/examples/dog-gallery/site/images/brown-dogs/brown-dog.jpg +0 -0
  197. package/examples/dog-gallery/site/images/brown-dogs/dog-accompanies-master.jpg +0 -0
  198. package/examples/dog-gallery/site/images/golden-dogs/golden-retriever.jpg +0 -0
  199. package/examples/dog-gallery/site/images/golden-dogs/toller-puppy.jpg +0 -0
  200. package/examples/dog-gallery/site/images/white-dogs/white-cute-dog.jpg +0 -0
  201. package/examples/dog-gallery/site/images/white-dogs/white-puppy-garden.png +0 -0
  202. package/examples/dog-gallery/site/public/favicon.svg +0 -7
  203. package/examples/dog-gallery/site/routes/010-dog-care/route-content.md +0 -38
  204. package/examples/dog-gallery/site/theme.md +0 -112
  205. package/examples/typography-presets/.astro/collections/site.schema.json +0 -776
  206. package/examples/typography-presets/.astro/data-store.json +0 -1
  207. package/examples/typography-presets/.astro/dev.log +0 -24
  208. package/examples/typography-presets/site/.norna/generated-images.json +0 -290
  209. package/examples/typography-presets/site/content.md +0 -134
  210. package/examples/typography-presets/site/theme.md +0 -43
  211. /package/examples/{typography-presets/.astro/content-modules.mjs → complete-sites/dog-shelter-multi-page/.astro/content-assets.mjs} +0 -0
  212. /package/examples/{typography-presets → complete-sites/dog-shelter-multi-page}/.astro/types.d.ts +0 -0
  213. /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/astro-D_GIpJIE.js +0 -0
  214. /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/astro-D_GIpJIE.js.map +0 -0
  215. /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/astro_n_aria-query.js +0 -0
  216. /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/astro_n_aria-query.js.map +0 -0
  217. /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/astro_n_axobject-query.js +0 -0
  218. /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/astro_n_axobject-query.js.map +0 -0
  219. /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/astro_n_html-escaper.js +0 -0
  220. /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/astro_n_html-escaper.js.map +0 -0
  221. /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +0 -0
  222. /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +0 -0
  223. /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/audit-DM9Hkl5B.js +0 -0
  224. /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/audit-DM9Hkl5B.js.map +0 -0
  225. /package/examples/{typography-presets → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/embla-carousel.js +0 -0
  226. /package/examples/{typography-presets → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/embla-carousel.js.map +0 -0
  227. /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/highlight-DnZ1ULOL.js +0 -0
  228. /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/highlight-DnZ1ULOL.js.map +0 -0
  229. /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/icons-RyebKi45.js +0 -0
  230. /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/icons-RyebKi45.js.map +0 -0
  231. /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/package.json +0 -0
  232. /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +0 -0
  233. /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/settings-DaU-gNnK.js +0 -0
  234. /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/settings-DaU-gNnK.js.map +0 -0
  235. /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/toolbar-63PA619H.js +0 -0
  236. /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/toolbar-63PA619H.js.map +0 -0
  237. /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/ui-library--bkcNSYz.js +0 -0
  238. /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/ui-library--bkcNSYz.js.map +0 -0
  239. /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/window-jPkpKVcy.js +0 -0
  240. /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/window-jPkpKVcy.js.map +0 -0
  241. /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/xray-BXCHpEJP.js +0 -0
  242. /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/xray-BXCHpEJP.js.map +0 -0
  243. /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/site/public/robots.txt +0 -0
  244. /package/examples/{typography-presets → feature-demos/media-and-surfaces}/.astro/content-assets.mjs +0 -0
  245. /package/examples/{typography-presets → feature-demos/media-and-surfaces}/node_modules/.vite/deps/package.json +0 -0
  246. /package/examples/{typography-presets → feature-demos/media-and-surfaces}/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +0 -0
  247. /package/examples/{typography-presets → feature-demos/theme-presets}/.astro/settings.json +0 -0
  248. /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/_metadata.json +0 -0
  249. /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/astro-D_GIpJIE.js +0 -0
  250. /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/astro-D_GIpJIE.js.map +0 -0
  251. /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/astro_n_aria-query.js +0 -0
  252. /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/astro_n_aria-query.js.map +0 -0
  253. /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/astro_n_axobject-query.js +0 -0
  254. /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/astro_n_axobject-query.js.map +0 -0
  255. /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/astro_n_html-escaper.js +0 -0
  256. /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/astro_n_html-escaper.js.map +0 -0
  257. /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +0 -0
  258. /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +0 -0
  259. /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/audit-DM9Hkl5B.js +0 -0
  260. /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/audit-DM9Hkl5B.js.map +0 -0
  261. /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/highlight-DnZ1ULOL.js +0 -0
  262. /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/highlight-DnZ1ULOL.js.map +0 -0
  263. /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/icons-RyebKi45.js +0 -0
  264. /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/icons-RyebKi45.js.map +0 -0
  265. /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/settings-DaU-gNnK.js +0 -0
  266. /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/settings-DaU-gNnK.js.map +0 -0
  267. /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/toolbar-63PA619H.js +0 -0
  268. /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/toolbar-63PA619H.js.map +0 -0
  269. /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/ui-library--bkcNSYz.js +0 -0
  270. /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/ui-library--bkcNSYz.js.map +0 -0
  271. /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/window-jPkpKVcy.js +0 -0
  272. /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/window-jPkpKVcy.js.map +0 -0
  273. /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/xray-BXCHpEJP.js +0 -0
  274. /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/xray-BXCHpEJP.js.map +0 -0
  275. /package/examples/{typography-presets → feature-demos/theme-presets}/site/public/robots.txt +0 -0
  276. /package/examples/{typography-presets/site/images/quiet-gallery → feature-demos/theme-presets/site/routes/010-portfolio/images/portfolio}/puppy-portrait.jpg +0 -0
  277. /package/examples/{typography-presets/site/images/quiet-gallery → feature-demos/theme-presets/site/routes/010-portfolio/images/portfolio}/shepherd-portrait.jpg +0 -0
  278. /package/examples/{typography-presets/site/images/quiet-gallery → feature-demos/theme-presets/site/routes/010-portfolio/images/portfolio}/white-puppy.jpg +0 -0
  279. /package/examples/{typography-presets/site/images/compact-gallery → feature-demos/theme-presets/site/routes/020-documentation/images/documentation}/black-schnauzer.jpg +0 -0
  280. /package/examples/{typography-presets/site/images/compact-gallery → feature-demos/theme-presets/site/routes/020-documentation/images/documentation}/retriever-portrait.jpg +0 -0
  281. /package/examples/{typography-presets/site/images/compact-gallery → feature-demos/theme-presets/site/routes/020-documentation/images/documentation}/schnauzer-portrait.jpg +0 -0
  282. /package/examples/{typography-presets/site/images/text-forward → feature-demos/theme-presets/site/routes/030-project/images/project}/boxer-portrait.jpg +0 -0
  283. /package/examples/{typography-presets/site/images/text-forward → feature-demos/theme-presets/site/routes/030-project/images/project}/grass-puppy.jpg +0 -0
  284. /package/examples/{typography-presets/site/images/text-forward → feature-demos/theme-presets/site/routes/030-project/images/project}/grey-street-dog.jpg +0 -0
  285. /package/examples/{typography-presets/site → feature-demos/theme-presets/site/routes/040-statement}/images/statement/black-pedigree.jpg +0 -0
  286. /package/examples/{typography-presets/site → feature-demos/theme-presets/site/routes/040-statement}/images/statement/sheepdog-grass.jpg +0 -0
  287. /package/examples/{typography-presets/site → feature-demos/theme-presets/site/routes/040-statement}/images/statement/sheepdog-wide.jpg +0 -0
@@ -129,6 +129,99 @@ test('content model v2 fixture checks and builds', async () => {
129
129
  }
130
130
  });
131
131
 
132
+ test('route theme replaces visual theme while site identity stays at the root', async () => {
133
+ const { root, siteDir } = await createTempSite({ underRepoCache: true });
134
+ try {
135
+ await writeFile(path.join(siteDir, 'theme.md'), `---
136
+ typography:
137
+ preset: text-forward
138
+ ---
139
+ `);
140
+ await writeFile(path.join(siteDir, 'sitewide-content.md'), `---
141
+ navigation:
142
+ brand: Root Brand
143
+ ---
144
+ `);
145
+ await mkdir(path.join(siteDir, 'routes', '010-guide'), { recursive: true });
146
+ await writeFile(path.join(siteDir, 'content.md'), `---
147
+ title: Root
148
+ description: Root page
149
+ ---
150
+
151
+ ## Home {#home}
152
+
153
+ Root content.
154
+ `);
155
+ await writeFile(path.join(siteDir, 'routes', '010-guide', 'route-content.md'), `---
156
+ title: Guide
157
+ description: Guide page
158
+ navigation:
159
+ label: Guide
160
+ ---
161
+
162
+ ## Guide {#guide}
163
+
164
+ Route content.
165
+ `);
166
+ await writeFile(path.join(siteDir, 'routes', '010-guide', 'theme.md'), `---
167
+ typography:
168
+ preset: statement
169
+ presentation:
170
+ palette: light
171
+ ---
172
+ `);
173
+
174
+ await runNorna(['--site-dir', siteDir, 'build']);
175
+ const rootHtml = await readFile(path.join(path.dirname(siteDir), 'dist', 'index.html'), 'utf8');
176
+ const routeHtml = await readFile(path.join(path.dirname(siteDir), 'dist', 'guide', 'index.html'), 'utf8');
177
+ assert.match(rootHtml, /--color-page: #000000/);
178
+ assert.match(routeHtml, /--color-page: #ffffff/);
179
+ assert.match(routeHtml, /<a class="site-brand" href="\/">Root Brand<\/a>/);
180
+ } finally {
181
+ await rm(root, { recursive: true, force: true });
182
+ }
183
+ });
184
+
185
+ test('route theme cannot define site identity', async () => {
186
+ const { root, siteDir } = await createTempSite({ underRepoCache: true });
187
+ try {
188
+ await mkdir(path.join(siteDir, 'routes', '010-guide'), { recursive: true });
189
+ await writeFile(path.join(siteDir, 'content.md'), `---
190
+ title: Root
191
+ description: Root page
192
+ ---
193
+
194
+ ## Home {#home}
195
+
196
+ Root content.
197
+ `);
198
+ await writeFile(path.join(siteDir, 'routes', '010-guide', 'route-content.md'), `---
199
+ title: Guide
200
+ description: Guide page
201
+ ---
202
+
203
+ ## Guide {#guide}
204
+
205
+ Route content.
206
+ `);
207
+ await writeFile(path.join(siteDir, 'routes', '010-guide', 'theme.md'), `---
208
+ navigation:
209
+ brand: Route Brand
210
+ ---
211
+ `);
212
+
213
+ await assert.rejects(
214
+ runNorna(['--site-dir', siteDir, 'config:check']),
215
+ (error) => {
216
+ assert.match(error.output, /may not define navigation\. Brand and logo belong in site\/sitewide-content\.md/);
217
+ return true;
218
+ },
219
+ );
220
+ } finally {
221
+ await rm(root, { recursive: true, force: true });
222
+ }
223
+ });
224
+
132
225
  test('content:check warns for local Markdown images but not external Markdown images', async () => {
133
226
  const { root, siteDir } = await createTempSite();
134
227
  try {
@@ -189,6 +282,40 @@ description: Fixture
189
282
  }
190
283
  });
191
284
 
285
+ test('inline notes render as linked numbered margin notes', async () => {
286
+ const { root, siteDir } = await createTempSite({ underRepoCache: true });
287
+ try {
288
+ await writeFile(path.join(siteDir, 'content.md'), `---
289
+ title: Inline notes
290
+ description: Fixture
291
+ ---
292
+
293
+ ## Intro {#intro}
294
+
295
+ The first paragraph points to an explanation.{note-ref}
296
+
297
+ {note: This explanation names \`sitewide-content.md\` and stays beside the paragraph on wide screens.}
298
+
299
+ The second paragraph has its own explanation.{note-ref}
300
+
301
+ {note: This is the second explanation.}
302
+ `);
303
+
304
+ await runNorna(['--site-dir', siteDir, 'build']);
305
+ const html = await readFile(path.join(path.dirname(siteDir), 'dist', 'index.html'), 'utf8');
306
+ assert.match(html, /<sup class="section-note-ref"><a id="note-ref-home-intro-1" href="#note-home-intro-1" aria-label="Note 1" aria-describedby="note-home-intro-1" data-note-id="note-home-intro-1">1<\/a><\/sup>/);
307
+ assert.match(html, /<aside class="section-note section-note-margin" id="note-home-intro-1" aria-label="Note 1" role="note" data-note-id="note-home-intro-1" style="grid-row: 1;">/);
308
+ assert.match(html, /<a class="section-note-number" href="#note-ref-home-intro-1" aria-label="Note 1">\s*1\s*<\/a>/);
309
+ assert.match(html, /This explanation names <code>sitewide-content\.md<\/code> and stays beside the paragraph on wide screens\./);
310
+ assert.match(html, /<sup class="section-note-ref"><a id="note-ref-home-intro-2" href="#note-home-intro-2" aria-label="Note 2" aria-describedby="note-home-intro-2" data-note-id="note-home-intro-2">2<\/a><\/sup>/);
311
+ assert.match(html, /<aside class="section-note section-note-margin" id="note-home-intro-2" aria-label="Note 2" role="note" data-note-id="note-home-intro-2" style="grid-row: 2;">/);
312
+ assert.match(html, /This is the second explanation\./);
313
+ assert.doesNotMatch(html, /\{note:/);
314
+ } finally {
315
+ await rm(root, { recursive: true, force: true });
316
+ }
317
+ });
318
+
192
319
  test('updated managed SVG images get updated static output', async () => {
193
320
  const { root, siteDir } = await createTempSite();
194
321
  try {
@@ -842,6 +969,33 @@ description: Fixture
842
969
  }
843
970
  });
844
971
 
972
+ test('content:check rejects the removed norna-note block', async () => {
973
+ const { root, siteDir } = await createTempSite();
974
+ try {
975
+ await writeFile(path.join(siteDir, 'content.md'), `---
976
+ title: Removed note block
977
+ description: Fixture
978
+ ---
979
+
980
+ ## Intro {#intro}
981
+
982
+ \`\`\`norna-note
983
+ This block is no longer supported.
984
+ \`\`\`
985
+ `);
986
+
987
+ await assert.rejects(
988
+ () => runContentScript(siteDir, ['--check']),
989
+ (error) => {
990
+ assert.match(error.output, /Unknown Norna block "norna-note"\. Use one of: norna-image-stack, norna-image-carousel, norna-card-list\./);
991
+ return true;
992
+ },
993
+ );
994
+ } finally {
995
+ await rm(root, { recursive: true, force: true });
996
+ }
997
+ });
998
+
845
999
  test('content:check supports tilde fenced Norna blocks', async () => {
846
1000
  const { root, siteDir } = await createTempSite();
847
1001
  try {
@@ -45,6 +45,7 @@ try {
45
45
  assert.equal(initializedPackageJson.dependencies['@janga/norna'], packageJson.version);
46
46
  assert.equal(initializedPackageJson.scripts['norna:engine:update'], 'norna engine:update');
47
47
  assert.equal(initializedPackageJson.scripts['norna:engine:version'], 'norna engine:version');
48
+ assert.equal(initializedPackageJson.scripts['norna:theme:export'], 'norna theme:export');
48
49
  assert.equal(initializedPackageJson.scripts['engine:update'], undefined);
49
50
  assert.equal(initializedPackageJson.scripts['engine:version'], undefined);
50
51
 
@@ -57,8 +58,10 @@ try {
57
58
  for (const [presetName, preset] of Object.entries(typographyPresets)) {
58
59
  for (const [level, heading] of Object.entries(preset.headings)) {
59
60
  assert.equal(heading.size, 'medium', `${presetName} ${level} should use medium size`);
61
+ assert.ok([400, 500, 600, 700].includes(heading.weight), `${presetName} ${level} should use a supported weight`);
60
62
  }
61
63
  assert.equal(preset.body.size, 'medium', `${presetName} body should use medium size`);
64
+ assert.ok(['narrow', 'normal', 'wide'].includes(preset.body.width), `${presetName} body should use a supported width`);
62
65
  assert.equal(preset.caption.size, 'medium', `${presetName} caption should use medium size`);
63
66
  }
64
67
  for (const [rhythmName, rhythm] of Object.entries(typographyRhythms)) {
@@ -76,7 +79,7 @@ try {
76
79
  assert.match(showResult.stdout, /pages:/);
77
80
  assert.match(showResult.stdout, /\s+\/:/);
78
81
  assert.match(showResult.stdout, /value: quiet-gallery/);
79
- assert.match(showResult.stdout, /inherited: true/);
82
+ assert.match(showResult.stdout, /source: "site\/theme\.md"/);
80
83
  assert.match(showResult.stdout, /intro:/);
81
84
 
82
85
  const initAgainResult = runCli(['init', initializedSiteRoot]);
@@ -0,0 +1,15 @@
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { getExampleSites } from './lib/example-sites.mjs';
4
+ import { runInherit } from './lib/run-command.mjs';
5
+
6
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
7
+ const cliPath = path.join(root, 'bin', 'norna.mjs');
8
+ const examples = await getExampleSites(root);
9
+
10
+ for (const example of examples) {
11
+ console.log(`\nBuilding ${example.siteLabel}`);
12
+ await runInherit(process.execPath, [cliPath, '--site-dir', example.siteLabel, 'build'], { cwd: root });
13
+ }
14
+
15
+ console.log(`\nok - built ${examples.length} example sites`);
@@ -1,15 +1,12 @@
1
- import { execFile, spawn } from 'node:child_process';
1
+ import { spawn } from 'node:child_process';
2
+ import net from 'node:net';
2
3
  import path from 'node:path';
3
4
  import { fileURLToPath } from 'node:url';
4
- import { promisify } from 'node:util';
5
-
6
- const execFileAsync = promisify(execFile);
7
5
 
8
6
  const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
9
7
  const cliPath = path.join(root, 'bin', 'norna.mjs');
10
- const dogGallerySiteDir = path.join(root, 'examples', 'dog-gallery', 'site');
11
- const url = 'http://localhost:4321/';
12
- const probeUrls = [url, 'http://127.0.0.1:4321/', 'http://[::1]:4321/'];
8
+ const navigationDemoSiteDir = path.join(root, 'examples', 'feature-demos', 'media-and-surfaces', 'site');
9
+ const host = '127.0.0.1';
13
10
  const npmBin = process.platform === 'win32' ? 'npm.cmd' : 'npm';
14
11
  const testTargets = process.argv.slice(2);
15
12
  const playwrightTargets = testTargets.length > 0 ? testTargets : ['tests/navigation.spec.ts'];
@@ -18,33 +15,46 @@ const sleep = (milliseconds) => new Promise((resolve) => {
18
15
  setTimeout(resolve, milliseconds);
19
16
  });
20
17
 
21
- const isReachable = async () => {
22
- for (const probeUrl of probeUrls) {
23
- try {
24
- const response = await fetch(probeUrl, { signal: AbortSignal.timeout(1_000) });
25
- await response.arrayBuffer();
26
- return response.ok;
27
- } catch {
28
- // Try the next loopback address.
29
- }
30
- }
18
+ const getAvailablePort = () => new Promise((resolve, reject) => {
19
+ const server = net.createServer();
20
+ server.unref();
21
+ server.once('error', reject);
22
+ server.listen(0, host, () => {
23
+ const address = server.address();
24
+ const port = typeof address === 'object' && address ? address.port : null;
25
+ server.close((error) => {
26
+ if (error) {
27
+ reject(error);
28
+ return;
29
+ }
30
+ resolve(port);
31
+ });
32
+ });
33
+ });
34
+
35
+ const port = await getAvailablePort();
36
+ if (!port) throw new Error('Could not reserve a port for the navigation test server.');
37
+ const url = `http://${host}:${port}/`;
31
38
 
39
+ const isReachable = async () => {
32
40
  try {
33
- await execFileAsync('curl', ['-fsI', url], {
34
- cwd: root,
35
- maxBuffer: 1024 * 1024,
36
- });
37
- return true;
41
+ const response = await fetch(url, { signal: AbortSignal.timeout(1_000) });
42
+ await response.arrayBuffer();
43
+ return response.ok;
38
44
  } catch {
39
45
  return false;
40
46
  }
41
47
  };
42
48
 
43
- const waitForServer = async () => {
49
+ const waitForServer = async (serverProcess) => {
44
50
  const startedAt = Date.now();
45
51
  const timeoutMs = 30_000;
46
52
 
47
53
  while (Date.now() - startedAt < timeoutMs) {
54
+ if (serverProcess.exitCode !== null) {
55
+ throw new Error(`Navigation test server exited before ${url} became reachable.`);
56
+ }
57
+
48
58
  if (await isReachable()) {
49
59
  return;
50
60
  }
@@ -55,12 +65,6 @@ const waitForServer = async () => {
55
65
  throw new Error(`Timed out waiting for ${url}`);
56
66
  };
57
67
 
58
- const runCapture = async (command, args, options = {}) => execFileAsync(command, args, {
59
- cwd: root,
60
- maxBuffer: 1024 * 1024 * 10,
61
- ...options,
62
- });
63
-
64
68
  const runInherit = (command, args, options = {}) => new Promise((resolve, reject) => {
65
69
  const child = spawn(command, args, {
66
70
  cwd: root,
@@ -82,31 +86,57 @@ const runInherit = (command, args, options = {}) => new Promise((resolve, reject
82
86
  });
83
87
  });
84
88
 
85
- const ensureServer = async () => {
86
- await stopServer();
89
+ const startServer = async () => {
90
+ await runInherit(process.execPath, [cliPath, 'site:public'], {
91
+ cwd: navigationDemoSiteDir,
92
+ });
93
+ await runInherit(process.execPath, [cliPath, 'images'], {
94
+ cwd: navigationDemoSiteDir,
95
+ });
87
96
 
88
- await runInherit(process.execPath, [cliPath, 'dev:local'], {
89
- cwd: dogGallerySiteDir,
97
+ const serverProcess = spawn(process.execPath, [
98
+ cliPath,
99
+ 'astro',
100
+ 'dev',
101
+ '--ignore-lock',
102
+ '--host',
103
+ host,
104
+ '--port',
105
+ String(port),
106
+ ], {
107
+ cwd: navigationDemoSiteDir,
108
+ stdio: 'inherit',
90
109
  env: {
91
110
  ...process.env,
92
- WALDE_NO_OPEN: '1',
111
+ ASTRO_DEV_BACKGROUND: '0',
93
112
  },
94
113
  });
95
- await waitForServer();
114
+ serverProcess.once('error', (error) => {
115
+ throw error;
116
+ });
117
+ await waitForServer(serverProcess);
96
118
 
97
- return true;
119
+ return serverProcess;
98
120
  };
99
121
 
100
- const stopServer = async () => {
101
- await runCapture(process.execPath, [cliPath, 'dev:stop'], {
102
- cwd: dogGallerySiteDir,
103
- }).catch(() => {});
122
+ const stopServer = async (serverProcess) => {
123
+ if (!serverProcess || serverProcess.exitCode !== null) return;
124
+
125
+ serverProcess.kill('SIGTERM');
126
+ const stopped = await Promise.race([
127
+ new Promise((resolve) => serverProcess.once('exit', () => resolve(true))),
128
+ sleep(5_000).then(() => false),
129
+ ]);
130
+
131
+ if (!stopped && serverProcess.exitCode === null) {
132
+ serverProcess.kill('SIGKILL');
133
+ }
104
134
  };
105
135
 
106
- let startedServer = false;
136
+ let serverProcess;
107
137
 
108
138
  try {
109
- startedServer = await ensureServer();
139
+ serverProcess = await startServer();
110
140
  await runInherit(npmBin, ['exec', '--', 'playwright', 'test', ...playwrightTargets], {
111
141
  env: {
112
142
  ...process.env,
@@ -114,7 +144,5 @@ try {
114
144
  },
115
145
  });
116
146
  } finally {
117
- if (startedServer) {
118
- await stopServer();
119
- }
147
+ await stopServer(serverProcess);
120
148
  }
@@ -160,6 +160,7 @@ try {
160
160
  assertFileExists(path.join(packagedStarterRoot, 'README.md')),
161
161
  assertFileExists(path.join(packagedStarterRoot, 'site', 'config.mjs')),
162
162
  assertFileExists(path.join(packagedStarterRoot, 'site', 'theme.md')),
163
+ assertFileExists(path.join(packagedStarterRoot, 'site', 'sitewide-content.md')),
163
164
  assertFileExists(path.join(packagedStarterRoot, 'site', 'content.md')),
164
165
  assertFileExists(path.join(packagedStarterRoot, 'site', 'images', 'work', '.gitkeep')),
165
166
  assertFileExists(path.join(packagedStarterRoot, 'site', 'public', 'robots.txt')),
@@ -184,11 +185,15 @@ try {
184
185
  .replace("url: 'https://example.com/'", "url: 'https://example.com/site/'")
185
186
  .replace("basePath: '/'", "basePath: '/site/'"),
186
187
  );
187
- const packageCheckThemePath = path.join(siteProjectRoot, 'site', 'theme.md');
188
- const packageCheckTheme = await readFile(packageCheckThemePath, 'utf8');
188
+ const packageCheckSitewidePath = path.join(siteProjectRoot, 'site', 'sitewide-content.md');
189
+ const packageCheckSitewide = await readFile(packageCheckSitewidePath, 'utf8');
189
190
  await writeFile(
190
- packageCheckThemePath,
191
- packageCheckTheme.replace('brand: Example Site', 'brand: Package Check Brand'),
191
+ packageCheckSitewidePath,
192
+ packageCheckSitewide.replace('brand: Example Site', 'brand: Package Check Brand\n logo:\n alt: Package Check Logo\n height: 2.6rem'),
193
+ );
194
+ await writeFile(
195
+ path.join(siteProjectRoot, 'site', 'public', 'logo.svg'),
196
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" fill="black"/></svg>\n',
192
197
  );
193
198
  await writeFile(path.join(siteProjectRoot, 'site', 'content.md'), `---
194
199
  title: Package Check Site
@@ -201,14 +206,14 @@ sections:
201
206
 
202
207
  ## Intro {#intro}
203
208
 
204
- This page verifies that [packaged norna sites]{.highlight} can build with the current content model.
209
+ This page verifies that **packaged norna sites** can build with the current content model.
205
210
 
206
211
  <!-- norna-image-provenance:
207
212
  image: package-check.svg
208
213
  source: package check fixture
209
214
  -->
210
215
 
211
- ## [**Warning**]{.highlight} {#warning}
216
+ ## **Warning** {#warning}
212
217
 
213
218
  This section verifies styled section headings.
214
219
 
@@ -262,6 +267,10 @@ This route verifies that packaged norna sites can build route pages.
262
267
  path.join(siteProjectRoot, 'dist', 'index.html'),
263
268
  '--section-heading-font-size-desktop: clamp(1.35rem, 1.1rem + 0.9vw, 2rem)',
264
269
  );
270
+ await assertFileIncludes(
271
+ path.join(siteProjectRoot, 'dist', 'index.html'),
272
+ '--section-heading-font-weight: 400',
273
+ );
265
274
  await assertFileIncludes(
266
275
  path.join(siteProjectRoot, 'dist', 'index.html'),
267
276
  '--section-heading-spacing-after: 0.55em',
@@ -288,7 +297,15 @@ This route verifies that packaged norna sites can build route pages.
288
297
  );
289
298
  await assertFileIncludes(
290
299
  path.join(siteProjectRoot, 'dist', 'index.html'),
291
- '<a class="site-brand" href="/site/">Package Check Brand</a>',
300
+ '<a class="site-brand" href="/site/"><img class="site-brand-logo" src="/site/logo.svg" alt="Package Check Logo">',
301
+ );
302
+ await assertFileIncludes(
303
+ path.join(siteProjectRoot, 'dist', 'index.html'),
304
+ '<div class="site-nav-submenu">',
305
+ );
306
+ await assertFileIncludes(
307
+ path.join(siteProjectRoot, 'dist', 'index.html'),
308
+ 'href="/site/#intro">Intro</a>',
292
309
  );
293
310
  await assertFileIncludes(
294
311
  path.join(siteProjectRoot, 'dist', 'index.html'),
@@ -298,6 +315,10 @@ This route verifies that packaged norna sites can build route pages.
298
315
  path.join(siteProjectRoot, 'dist', 'index.html'),
299
316
  '--section-body-line-height: 1.5',
300
317
  );
318
+ await assertFileIncludes(
319
+ path.join(siteProjectRoot, 'dist', 'index.html'),
320
+ '--section-body-width-desktop: min(72ch, var(--text-width))',
321
+ );
301
322
  await assertFileExcludes(
302
323
  path.join(siteProjectRoot, 'dist', 'index.html'),
303
324
  'norna-image-provenance',
@@ -318,25 +339,29 @@ This route verifies that packaged norna sites can build route pages.
318
339
  path.join(siteProjectRoot, 'dist', 'index.html'),
319
340
  '--section-caption-font-size-desktop: clamp(0.86rem, 0.84rem + 0.08vw, 0.92rem)',
320
341
  );
342
+ await assertFileExcludes(
343
+ path.join(siteProjectRoot, 'dist', 'index.html'),
344
+ 'class="inline-style',
345
+ );
321
346
  await assertFileIncludes(
322
347
  path.join(siteProjectRoot, 'dist', 'index.html'),
323
- 'class="inline-style inline-style-highlight" style="--inline-style-color: #ffd84d"',
348
+ 'data-section-title="Warning"',
324
349
  );
325
350
  await assertFileIncludes(
326
351
  path.join(siteProjectRoot, 'dist', 'index.html'),
327
- '<h2><span class="inline-style inline-style-highlight" style="--inline-style-color: #ffd84d"><strong>Warning</strong></span></h2>',
352
+ 'href="/site/#warning">Warning</a>',
328
353
  );
329
354
  await assertFileIncludes(
330
355
  path.join(siteProjectRoot, 'dist', 'index.html'),
331
- 'data-section-title="Warning"',
356
+ '--section-background-color: #000000',
332
357
  );
333
358
  await assertFileIncludes(
334
359
  path.join(siteProjectRoot, 'dist', 'index.html'),
335
- 'href="#warning">Warning</a>',
360
+ '--section-background-color: #171717',
336
361
  );
337
362
  await assertFileIncludes(
338
363
  path.join(siteProjectRoot, 'dist', 'index.html'),
339
- '--section-background-color: #000000',
364
+ '--section-background-color: #252525',
340
365
  );
341
366
  await assertFileIncludes(
342
367
  path.join(siteProjectRoot, 'dist', 'index.html'),
@@ -344,7 +369,7 @@ This route verifies that packaged norna sites can build route pages.
344
369
  );
345
370
  await assertFileIncludes(
346
371
  path.join(siteProjectRoot, 'dist', 'index.html'),
347
- '--site-top-text-color: #f7f4ee',
372
+ '--site-top-text-color: #f2eee6',
348
373
  );
349
374
  await assertFileIncludes(
350
375
  path.join(siteProjectRoot, 'dist', 'index.html'),
@@ -352,11 +377,11 @@ This route verifies that packaged norna sites can build route pages.
352
377
  );
353
378
  await assertFileIncludes(
354
379
  path.join(siteProjectRoot, 'dist', 'index.html'),
355
- '--site-footer-text-color: #f7f4ee',
380
+ '--site-footer-text-color: #f2eee6',
356
381
  );
357
382
  await assertFileIncludes(
358
383
  path.join(siteProjectRoot, 'dist', 'index.html'),
359
- '--section-text-color: #f7f4ee',
384
+ '--section-text-color: #f2eee6',
360
385
  );
361
386
  await writeFile(path.join(siteProjectRoot, 'site', 'public', 'favicon.ico'), 'fake icon');
362
387
  await runInherit(npxBin, ['norna', 'build'], { cwd: siteProjectRoot, env: npmEnv });
@@ -382,35 +407,52 @@ This route verifies that packaged norna sites can build route pages.
382
407
  );
383
408
  await writeFile(
384
409
  siteThemePath,
385
- siteTheme.replace('backgroundColor: "#000000"', 'backgroundColor: blue'),
410
+ siteTheme.replace('\n palette: dark', '\n palette: neon'),
386
411
  );
387
412
  await runExpectFailure(
388
413
  npxBin,
389
414
  ['norna', 'build'],
390
415
  { cwd: siteProjectRoot, env: npmEnv },
391
- 'presentation.backgroundColor',
416
+ 'presentation.palette',
392
417
  );
393
418
  await writeFile(
394
419
  siteThemePath,
395
- siteTheme.replace('textColor: "#f7f4ee"', 'textColor: white'),
420
+ siteTheme.replace(' sectionSurfaces:\n mode: cycle', ' sectionSurfaces:\n mode: glowing'),
396
421
  );
397
422
  await runExpectFailure(
398
423
  npxBin,
399
424
  ['norna', 'build'],
400
425
  { cwd: siteProjectRoot, env: npmEnv },
401
- 'presentation.textColor',
426
+ 'presentation.sectionSurfaces.mode',
402
427
  );
403
428
  await writeFile(
404
429
  siteThemePath,
405
- siteTheme.replace(' color: "#ffd84d"', ' color: yellow'),
430
+ siteTheme.replace(
431
+ ' sectionSurfaces:\n mode: cycle',
432
+ ' sectionSurfaces:\n mode: cycle\n sequence: [base, base]',
433
+ ),
406
434
  );
407
435
  await runExpectFailure(
408
436
  npxBin,
409
437
  ['norna', 'build'],
410
438
  { cwd: siteProjectRoot, env: npmEnv },
411
- 'presentation.inlineStyles.highlight.color',
439
+ 'presentation.sectionSurfaces',
412
440
  );
413
441
  await writeFile(siteThemePath, siteTheme);
442
+ await writeFile(
443
+ siteContentPath,
444
+ siteContent.replace(
445
+ 'This page verifies that **packaged norna sites** can build with the current content model.',
446
+ 'This page verifies that [packaged norna sites]{.highlight} can build with the current content model.',
447
+ ),
448
+ );
449
+ await runExpectFailure(
450
+ npxBin,
451
+ ['norna', 'build'],
452
+ { cwd: siteProjectRoot, env: npmEnv },
453
+ 'Inline color style ".highlight" is no longer supported',
454
+ );
455
+ await writeFile(siteContentPath, siteContent);
414
456
  await writeFile(
415
457
  siteContentPath,
416
458
  siteContent.replace(
@@ -422,7 +464,7 @@ This route verifies that packaged norna sites can build route pages.
422
464
  npxBin,
423
465
  ['norna', 'build'],
424
466
  { cwd: siteProjectRoot, env: npmEnv },
425
- 'presentation.typography.overrides.body.lineHeight',
467
+ 'presentation',
426
468
  );
427
469
  await writeFile(
428
470
  siteContentPath,
@@ -435,7 +477,7 @@ This route verifies that packaged norna sites can build route pages.
435
477
  npxBin,
436
478
  ['norna', 'build'],
437
479
  { cwd: siteProjectRoot, env: npmEnv },
438
- 'sections.intro.presentation.typography.overrides.caption.spacingBefore',
480
+ 'sections.intro: Unrecognized key: "presentation"',
439
481
  );
440
482
  await writeFile(
441
483
  siteContentPath,
@@ -448,7 +490,7 @@ This route verifies that packaged norna sites can build route pages.
448
490
  npxBin,
449
491
  ['norna', 'build'],
450
492
  { cwd: siteProjectRoot, env: npmEnv },
451
- 'sections.intro.presentation.typography.preset',
493
+ 'sections.intro: Unrecognized key: "presentation"',
452
494
  );
453
495
  await writeFile(
454
496
  siteThemePath,
@@ -51,8 +51,23 @@ try {
51
51
  await writeFile(path.join(siteDir, 'theme.md'), `---
52
52
  typography:
53
53
  preset: quiet-gallery
54
- frame:
55
- colors: presentation
54
+ presentation:
55
+ palette: dark
56
+ ---
57
+ `);
58
+ await writeFile(path.join(siteDir, 'sitewide-content.md'), `---
59
+ banners:
60
+ - id: expired-banner
61
+ visible:
62
+ until: "2026-01-01"
63
+ title: Expired banner
64
+ text: Expired banner text.
65
+ - id: active-banner
66
+ visible:
67
+ from: "2026-01-01"
68
+ until: "2026-12-31"
69
+ title: Active banner
70
+ text: Active banner text.
56
71
  ---
57
72
  `);
58
73
  await writeFile(path.join(siteDir, 'content.md'), `---
@@ -88,6 +103,8 @@ Always visible section text.
88
103
  assert.match(html, /Active section text/);
89
104
  assert.match(html, /Always visible section text/);
90
105
  assert.doesNotMatch(html, /Expired section text/);
106
+ assert.match(html, /Active banner text/);
107
+ assert.doesNotMatch(html, /Expired banner text/);
91
108
 
92
109
  console.log('Temporary visibility test passed.');
93
110
  } finally {