@janga/norna 0.7.8 → 0.7.10

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 (234) hide show
  1. package/README.md +1 -1
  2. package/docs/README.md +18 -11
  3. package/docs/configuration.md +9 -54
  4. package/docs/content.md +6 -7
  5. package/docs/design/norna-diagram-design.md +158 -0
  6. package/docs/getting-started.md +18 -16
  7. package/docs/routes.md +7 -6
  8. package/docs/site-structure.md +4 -2
  9. package/docs/sitewide-content.md +74 -0
  10. package/docs/theme.md +61 -128
  11. package/docs/typography.md +45 -33
  12. package/examples/001-dog-shelter-single-page/README.md +9 -0
  13. package/examples/001-dog-shelter-single-page/site/config.mjs +27 -0
  14. package/examples/001-dog-shelter-single-page/site/content.md +38 -0
  15. package/examples/001-dog-shelter-single-page/site/images/what-we-do/dog-house.svg +25 -0
  16. package/examples/001-dog-shelter-single-page/site/images/you-can-help/heart.svg +13 -0
  17. package/examples/001-dog-shelter-single-page/site/public/favicon.svg +10 -0
  18. package/examples/001-dog-shelter-single-page/site/public/robots.txt +2 -0
  19. package/examples/001-dog-shelter-single-page/site/sitewide-content.md +6 -0
  20. package/examples/001-dog-shelter-single-page/site/theme.md +25 -0
  21. package/examples/002-dog-shelter-multi-page/README.md +9 -0
  22. package/examples/002-dog-shelter-multi-page/site/config.mjs +27 -0
  23. package/examples/002-dog-shelter-multi-page/site/content.md +12 -0
  24. package/examples/002-dog-shelter-multi-page/site/public/favicon.svg +10 -0
  25. package/examples/002-dog-shelter-multi-page/site/public/robots.txt +2 -0
  26. package/examples/002-dog-shelter-multi-page/site/routes/010-dogs/images/dogs/rex.svg +19 -0
  27. package/examples/002-dog-shelter-multi-page/site/routes/010-dogs/images/dogs/rover.svg +19 -0
  28. package/examples/002-dog-shelter-multi-page/site/routes/010-dogs/route-content.md +30 -0
  29. package/examples/002-dog-shelter-multi-page/site/routes/020-adopt/route-content.md +12 -0
  30. package/examples/002-dog-shelter-multi-page/site/sitewide-content.md +6 -0
  31. package/examples/002-dog-shelter-multi-page/site/theme.md +25 -0
  32. package/examples/dog-gallery/.astro/collections/site.schema.json +776 -0
  33. package/examples/dog-gallery/.astro/collections/theme.schema.json +672 -0
  34. package/examples/dog-gallery/.astro/content-assets.mjs +4 -0
  35. package/examples/dog-gallery/.astro/content-modules.mjs +1 -0
  36. package/examples/dog-gallery/.astro/content.d.ts +175 -0
  37. package/examples/dog-gallery/.astro/data-store.json +1 -0
  38. package/examples/dog-gallery/.astro/dev-local.json +8 -0
  39. package/examples/dog-gallery/.astro/dev.json +13 -0
  40. package/examples/dog-gallery/.astro/dev.log +95 -0
  41. package/examples/dog-gallery/.astro/settings.json +5 -0
  42. package/examples/dog-gallery/.astro/types.d.ts +2 -0
  43. package/examples/dog-gallery/dist/_astro/GalleryGrid.astro_astro_type_script_index_0_lang.B0kxDyGz.js +1 -0
  44. package/examples/dog-gallery/dist/_astro/SitePage.DicGh_5N.css +1 -0
  45. package/examples/dog-gallery/dist/dog-care/index.html +15 -0
  46. package/examples/dog-gallery/dist/dog-shelter-logo.svg +14 -0
  47. package/examples/dog-gallery/dist/favicon.svg +7 -0
  48. package/examples/dog-gallery/dist/images/generated/black-dogs/black-puppy-meadow-7255e3d9-1080.webp +0 -0
  49. package/examples/dog-gallery/dist/images/generated/black-dogs/black-puppy-meadow-7255e3d9-1440.webp +0 -0
  50. package/examples/dog-gallery/dist/images/generated/black-dogs/black-puppy-meadow-7255e3d9-1536.webp +0 -0
  51. package/examples/dog-gallery/dist/images/generated/black-dogs/black-puppy-meadow-7255e3d9-480.webp +0 -0
  52. package/examples/dog-gallery/dist/images/generated/black-dogs/black-puppy-meadow-7255e3d9-768.webp +0 -0
  53. package/examples/dog-gallery/dist/images/generated/black-dogs/photo-of-a-black-dog-5cf614d2-480.webp +0 -0
  54. package/examples/dog-gallery/dist/images/generated/black-dogs/photo-of-a-black-dog-5cf614d2-768.webp +0 -0
  55. package/examples/dog-gallery/dist/images/generated/black-dogs/photo-of-a-black-dog-5cf614d2-960.webp +0 -0
  56. package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-1080.webp +0 -0
  57. package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-1440.webp +0 -0
  58. package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-1920.webp +0 -0
  59. package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-3939.webp +0 -0
  60. package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-480.webp +0 -0
  61. package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-768.webp +0 -0
  62. package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-1080.webp +0 -0
  63. package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-1440.webp +0 -0
  64. package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-1920.webp +0 -0
  65. package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-2400.webp +0 -0
  66. package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-480.webp +0 -0
  67. package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-768.webp +0 -0
  68. package/examples/dog-gallery/dist/images/generated/golden-dogs/golden-retriever-f797fd88-1080.webp +0 -0
  69. package/examples/dog-gallery/dist/images/generated/golden-dogs/golden-retriever-f797fd88-1440.webp +0 -0
  70. package/examples/dog-gallery/dist/images/generated/golden-dogs/golden-retriever-f797fd88-1600.webp +0 -0
  71. package/examples/dog-gallery/dist/images/generated/golden-dogs/golden-retriever-f797fd88-480.webp +0 -0
  72. package/examples/dog-gallery/dist/images/generated/golden-dogs/golden-retriever-f797fd88-768.webp +0 -0
  73. package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-1080.webp +0 -0
  74. package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-1440.webp +0 -0
  75. package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-1920.webp +0 -0
  76. package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-2576.webp +0 -0
  77. package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-480.webp +0 -0
  78. package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-768.webp +0 -0
  79. package/examples/dog-gallery/dist/images/generated/white-dogs/white-cute-dog-077a0689-1080.webp +0 -0
  80. package/examples/dog-gallery/dist/images/generated/white-dogs/white-cute-dog-077a0689-1277.webp +0 -0
  81. package/examples/dog-gallery/dist/images/generated/white-dogs/white-cute-dog-077a0689-480.webp +0 -0
  82. package/examples/dog-gallery/dist/images/generated/white-dogs/white-cute-dog-077a0689-768.webp +0 -0
  83. package/examples/dog-gallery/dist/images/generated/white-dogs/white-puppy-garden-d3ab2ca0-1080.webp +0 -0
  84. package/examples/dog-gallery/dist/images/generated/white-dogs/white-puppy-garden-d3ab2ca0-1440.webp +0 -0
  85. package/examples/dog-gallery/dist/images/generated/white-dogs/white-puppy-garden-d3ab2ca0-1536.webp +0 -0
  86. package/examples/dog-gallery/dist/images/generated/white-dogs/white-puppy-garden-d3ab2ca0-480.webp +0 -0
  87. package/examples/dog-gallery/dist/images/generated/white-dogs/white-puppy-garden-d3ab2ca0-768.webp +0 -0
  88. package/examples/dog-gallery/dist/index.html +45 -0
  89. package/examples/dog-gallery/dist/robots.txt +2 -0
  90. package/examples/dog-gallery/node_modules/.vite/deps/_metadata.json +7 -7
  91. package/examples/dog-gallery/site/.norna/generated-images.json +8 -8
  92. package/examples/dog-gallery/site/config.mjs +0 -20
  93. package/examples/dog-gallery/site/content.md +4 -5
  94. package/examples/dog-gallery/site/public/logo.svg +14 -0
  95. package/examples/dog-gallery/site/sitewide-content.md +16 -0
  96. package/examples/dog-gallery/site/theme.md +13 -24
  97. package/examples/presentation/README.md +15 -0
  98. package/examples/presentation/site/.norna/generated-images.json +66 -0
  99. package/examples/presentation/site/config.mjs +29 -0
  100. package/examples/presentation/site/content.md +67 -0
  101. package/examples/presentation/site/public/robots.txt +2 -0
  102. package/examples/presentation/site/routes/010-presets/route-content.md +63 -0
  103. package/examples/presentation/site/routes/010-presets/theme.md +4 -0
  104. package/examples/presentation/site/routes/020-media/images/cards/card-carousel.svg +11 -0
  105. package/examples/presentation/site/routes/020-media/images/cards/card-stack.svg +9 -0
  106. package/examples/presentation/site/routes/020-media/images/cards/card-surfaces.svg +8 -0
  107. package/examples/presentation/site/routes/020-media/images/carousel/carousel-one.svg +8 -0
  108. package/examples/presentation/site/routes/020-media/images/carousel/carousel-three.svg +13 -0
  109. package/examples/presentation/site/routes/020-media/images/carousel/carousel-two.svg +8 -0
  110. package/examples/presentation/site/routes/020-media/images/stack/stack-one.svg +8 -0
  111. package/examples/presentation/site/routes/020-media/images/stack/stack-two.svg +9 -0
  112. package/examples/presentation/site/routes/020-media/route-content.md +77 -0
  113. package/examples/presentation/site/routes/020-media/theme.md +4 -0
  114. package/examples/presentation/site/routes/030-surfaces/route-content.md +44 -0
  115. package/examples/presentation/site/routes/030-surfaces/theme.md +7 -0
  116. package/examples/presentation/site/sitewide-content.md +6 -0
  117. package/examples/presentation/site/theme.md +43 -0
  118. package/examples/typography-presets/.astro/collections/routeTheme.schema.json +746 -0
  119. package/examples/typography-presets/.astro/collections/site.schema.json +59 -0
  120. package/examples/typography-presets/.astro/collections/sitewide.schema.json +114 -0
  121. package/examples/typography-presets/.astro/collections/theme.schema.json +770 -0
  122. package/examples/typography-presets/.astro/content-assets.mjs +4 -0
  123. package/examples/typography-presets/.astro/content-modules.mjs +1 -0
  124. package/examples/typography-presets/.astro/content.d.ts +183 -0
  125. package/examples/typography-presets/.astro/data-store.json +1 -0
  126. package/examples/typography-presets/.astro/dev-local.json +8 -0
  127. package/examples/typography-presets/.astro/dev.json +15 -0
  128. package/examples/typography-presets/.astro/dev.log +45 -0
  129. package/examples/typography-presets/.astro/settings.json +5 -0
  130. package/examples/typography-presets/.astro/types.d.ts +2 -0
  131. package/examples/typography-presets/node_modules/.vite/deps/_metadata.json +80 -0
  132. package/examples/typography-presets/node_modules/.vite/deps/astro-D_GIpJIE.js +434 -0
  133. package/examples/typography-presets/node_modules/.vite/deps/astro-D_GIpJIE.js.map +1 -0
  134. package/examples/typography-presets/node_modules/.vite/deps/astro_n_aria-query.js +6390 -0
  135. package/examples/typography-presets/node_modules/.vite/deps/astro_n_aria-query.js.map +1 -0
  136. package/examples/typography-presets/node_modules/.vite/deps/astro_n_axobject-query.js +2702 -0
  137. package/examples/typography-presets/node_modules/.vite/deps/astro_n_axobject-query.js.map +1 -0
  138. package/examples/typography-presets/node_modules/.vite/deps/astro_n_html-escaper.js +65 -0
  139. package/examples/typography-presets/node_modules/.vite/deps/astro_n_html-escaper.js.map +1 -0
  140. package/examples/typography-presets/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +455 -0
  141. package/examples/typography-presets/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +1 -0
  142. package/examples/typography-presets/node_modules/.vite/deps/audit-DM9Hkl5B.js +1534 -0
  143. package/examples/typography-presets/node_modules/.vite/deps/audit-DM9Hkl5B.js.map +1 -0
  144. package/examples/typography-presets/node_modules/.vite/deps/embla-carousel.js +1389 -0
  145. package/examples/typography-presets/node_modules/.vite/deps/embla-carousel.js.map +1 -0
  146. package/examples/typography-presets/node_modules/.vite/deps/highlight-DnZ1ULOL.js +48 -0
  147. package/examples/typography-presets/node_modules/.vite/deps/highlight-DnZ1ULOL.js.map +1 -0
  148. package/examples/typography-presets/node_modules/.vite/deps/icons-RyebKi45.js +42 -0
  149. package/examples/typography-presets/node_modules/.vite/deps/icons-RyebKi45.js.map +1 -0
  150. package/examples/typography-presets/node_modules/.vite/deps/package.json +3 -0
  151. package/examples/typography-presets/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +4 -0
  152. package/examples/typography-presets/node_modules/.vite/deps/settings-DaU-gNnK.js +190 -0
  153. package/examples/typography-presets/node_modules/.vite/deps/settings-DaU-gNnK.js.map +1 -0
  154. package/examples/typography-presets/node_modules/.vite/deps/toolbar-63PA619H.js +458 -0
  155. package/examples/typography-presets/node_modules/.vite/deps/toolbar-63PA619H.js.map +1 -0
  156. package/examples/typography-presets/node_modules/.vite/deps/ui-library--bkcNSYz.js +1016 -0
  157. package/examples/typography-presets/node_modules/.vite/deps/ui-library--bkcNSYz.js.map +1 -0
  158. package/examples/typography-presets/node_modules/.vite/deps/window-jPkpKVcy.js +32 -0
  159. package/examples/typography-presets/node_modules/.vite/deps/window-jPkpKVcy.js.map +1 -0
  160. package/examples/typography-presets/node_modules/.vite/deps/xray-BXCHpEJP.js +111 -0
  161. package/examples/typography-presets/node_modules/.vite/deps/xray-BXCHpEJP.js.map +1 -0
  162. package/examples/typography-presets/site/.norna/generated-images.json +290 -0
  163. package/examples/typography-presets/site/config.mjs +29 -0
  164. package/examples/typography-presets/site/content.md +16 -0
  165. package/examples/typography-presets/site/public/robots.txt +2 -0
  166. package/examples/typography-presets/site/routes/010-quiet-gallery/images/quiet-gallery/puppy-portrait.jpg +0 -0
  167. package/examples/typography-presets/site/routes/010-quiet-gallery/images/quiet-gallery/shepherd-portrait.jpg +0 -0
  168. package/examples/typography-presets/site/routes/010-quiet-gallery/images/quiet-gallery/white-puppy.jpg +0 -0
  169. package/examples/typography-presets/site/routes/010-quiet-gallery/route-content.md +34 -0
  170. package/examples/typography-presets/site/routes/010-quiet-gallery/theme.md +4 -0
  171. package/examples/typography-presets/site/routes/020-compact-gallery/images/compact-gallery/black-schnauzer.jpg +0 -0
  172. package/examples/typography-presets/site/routes/020-compact-gallery/images/compact-gallery/retriever-portrait.jpg +0 -0
  173. package/examples/typography-presets/site/routes/020-compact-gallery/images/compact-gallery/schnauzer-portrait.jpg +0 -0
  174. package/examples/typography-presets/site/routes/020-compact-gallery/route-content.md +33 -0
  175. package/examples/typography-presets/site/routes/020-compact-gallery/theme.md +4 -0
  176. package/examples/typography-presets/site/routes/030-text-forward/images/text-forward/boxer-portrait.jpg +0 -0
  177. package/examples/typography-presets/site/routes/030-text-forward/images/text-forward/grass-puppy.jpg +0 -0
  178. package/examples/typography-presets/site/routes/030-text-forward/images/text-forward/grey-street-dog.jpg +0 -0
  179. package/examples/typography-presets/site/routes/030-text-forward/route-content.md +34 -0
  180. package/examples/typography-presets/site/routes/030-text-forward/theme.md +4 -0
  181. package/examples/typography-presets/site/routes/040-statement/images/statement/black-pedigree.jpg +0 -0
  182. package/examples/typography-presets/site/routes/040-statement/images/statement/sheepdog-grass.jpg +0 -0
  183. package/examples/typography-presets/site/routes/040-statement/images/statement/sheepdog-wide.jpg +0 -0
  184. package/examples/typography-presets/site/routes/040-statement/route-content.md +34 -0
  185. package/examples/typography-presets/site/routes/040-statement/theme.md +4 -0
  186. package/examples/typography-presets/site/sitewide-content.md +14 -0
  187. package/examples/typography-presets/site/theme.md +43 -0
  188. package/fixtures/basic/site/config.mjs +0 -13
  189. package/fixtures/basic/site/sitewide-content.md +12 -0
  190. package/fixtures/basic/site/theme.md +2 -2
  191. package/fixtures/content-model-v2/site/content.md +2 -5
  192. package/fixtures/content-model-v2/site/theme.md +1 -3
  193. package/package.json +3 -2
  194. package/scripts/check-config.mjs +22 -1
  195. package/scripts/lib/frontmatter-yaml.mjs +28 -0
  196. package/scripts/lib/logo-assets.mjs +35 -0
  197. package/scripts/lib/norna-markdown-blocks.mjs +166 -0
  198. package/scripts/lib/presentation.mjs +104 -46
  199. package/scripts/lib/project-config.mjs +67 -105
  200. package/scripts/lib/site-content.mjs +26 -42
  201. package/scripts/lib/site-paths.mjs +2 -0
  202. package/scripts/lib/sitewide-config.mjs +34 -0
  203. package/scripts/lib/theme-config.mjs +72 -6
  204. package/scripts/lib/typography.mjs +20 -26
  205. package/scripts/show-typography.mjs +40 -147
  206. package/scripts/sync-content-sections.mjs +19 -14
  207. package/scripts/test-content-check.mjs +76 -14
  208. package/scripts/test-content-model-v2.mjs +154 -0
  209. package/scripts/test-engine-commands.mjs +3 -1
  210. package/scripts/test-package-check.mjs +78 -19
  211. package/scripts/test-temporary-visibility.mjs +19 -2
  212. package/src/components/CardList.astro +5 -2
  213. package/src/components/GalleryGrid.astro +37 -16
  214. package/src/components/SiteBanners.astro +100 -0
  215. package/src/components/SiteNavigation.astro +48 -31
  216. package/src/components/SitePage.astro +55 -13
  217. package/src/components/SiteSection.astro +194 -36
  218. package/src/content.config.ts +78 -48
  219. package/src/layouts/BaseLayout.astro +64 -14
  220. package/src/lib/routeThemes.ts +61 -0
  221. package/src/lib/sectionContent.ts +236 -44
  222. package/src/lib/sitePublicAssets.ts +1 -0
  223. package/src/styles/global.css +282 -93
  224. package/starters/basic/.github/workflows/deploy.yml +1 -1
  225. package/starters/basic/README.md +7 -6
  226. package/starters/basic/site/config.mjs +0 -13
  227. package/starters/basic/site/content.md +1 -1
  228. package/starters/basic/site/sitewide-content.md +14 -0
  229. package/starters/basic/site/theme.md +13 -24
  230. package/starters/project/.github/workflows/deploy.yml +1 -1
  231. package/starters/project/README.md +10 -8
  232. package/starters/project/site/config.mjs +0 -13
  233. package/starters/project/site/sitewide-content.md +14 -0
  234. package/starters/project/site/theme.md +3 -11
@@ -0,0 +1,34 @@
1
+ ---
2
+ title: Quiet Gallery
3
+ description: A restrained typography preset for image-led pages.
4
+ navigation:
5
+ label: Quiet Gallery
6
+ ---
7
+
8
+ ## Quiet Gallery {#quiet-gallery}
9
+
10
+ This route uses the `quiet-gallery` preset. Its purpose is to keep text
11
+ restrained so images can carry much of the page without the typography
12
+ competing for attention.
13
+
14
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a
15
+ ante venenatis dapibus posuere velit aliquet. Maecenas faucibus mollis
16
+ interdum. Donec ullamcorper nulla non metus auctor fringilla.
17
+
18
+ ### Reading rhythm
19
+
20
+ Curabitur blandit tempus porttitor. Praesent commodo cursus magna, vel
21
+ scelerisque nisl consectetur et. Vestibulum id ligula porta felis euismod
22
+ semper.
23
+
24
+ ```norna-image-stack
25
+ - image: puppy-portrait.jpg
26
+ alt: A puppy sitting outdoors and looking toward the camera.
27
+ caption: Puppy portrait. Photo by Bicanski, CC0, via Pixnio.
28
+ - image: shepherd-portrait.jpg
29
+ alt: A shepherd dog standing outdoors and looking toward the camera.
30
+ caption: Shepherd dog portrait. Photo by Bicanski, CC0, via Pixnio.
31
+ - image: white-puppy.jpg
32
+ alt: A white puppy outdoors in winter.
33
+ caption: White puppy outdoors. Photo by Bicanski, CC0, via Pixnio.
34
+ ```
@@ -0,0 +1,4 @@
1
+ ---
2
+ typography:
3
+ preset: quiet-gallery
4
+ ---
@@ -0,0 +1,33 @@
1
+ ---
2
+ title: Compact Gallery
3
+ description: A compact typography preset for dense image-led pages.
4
+ navigation:
5
+ label: Compact Gallery
6
+ ---
7
+
8
+ ## Compact Gallery {#compact-gallery}
9
+
10
+ This route uses the `compact-gallery` preset. Its purpose is to make denser
11
+ image-led pages easier to scan when a site contains many sections, many images
12
+ or short information blocks.
13
+
14
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean lacinia
15
+ bibendum nulla sed consectetur. Etiam porta sem malesuada magna mollis euismod.
16
+
17
+ ### Dense sections
18
+
19
+ Nullam id dolor id nibh ultricies vehicula ut id elit. Fusce dapibus, tellus ac
20
+ cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit
21
+ amet risus.
22
+
23
+ ```norna-image-stack
24
+ - image: schnauzer-portrait.jpg
25
+ alt: A schnauzer-type dog sitting outdoors.
26
+ caption: Schnauzer portrait. Photo by Bicanski, CC0, via Pixnio.
27
+ - image: black-schnauzer.jpg
28
+ alt: A black schnauzer-type dog standing on grass.
29
+ caption: Black hunting dog portrait. Photo by Bicanski, CC0, via Pixnio.
30
+ - image: retriever-portrait.jpg
31
+ alt: A retriever-type dog sitting outdoors.
32
+ caption: Retriever portrait. Photo by Bicanski, CC0, via Pixnio.
33
+ ```
@@ -0,0 +1,4 @@
1
+ ---
2
+ typography:
3
+ preset: compact-gallery
4
+ ---
@@ -0,0 +1,34 @@
1
+ ---
2
+ title: Text Forward
3
+ description: A typography preset for explanatory writing.
4
+ navigation:
5
+ label: Text Forward
6
+ ---
7
+
8
+ ## Text Forward {#text-forward}
9
+
10
+ This route uses the `text-forward` preset. Its purpose is to give longer
11
+ explanatory text more presence while still supporting image captions and
12
+ image-led sections.
13
+
14
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed posuere
15
+ consectetur est at lobortis. Cras mattis consectetur purus sit amet fermentum.
16
+ Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.
17
+
18
+ ### Longer explanations
19
+
20
+ Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Donec sed odio
21
+ dui. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget
22
+ lacinia odio sem nec elit.
23
+
24
+ ```norna-image-stack
25
+ - image: grass-puppy.jpg
26
+ alt: A puppy standing in green grass.
27
+ caption: Puppy in grass. Photo by Bicanski, CC0, via Pixnio.
28
+ - image: grey-street-dog.jpg
29
+ alt: A grey dog standing on a paved street.
30
+ caption: Grey dog on pavement. Photo by Bicanski, CC0, via Pixnio.
31
+ - image: boxer-portrait.jpg
32
+ alt: A brown boxer-type dog looking at the camera.
33
+ caption: Brown dog portrait. Photo by Bicanski, CC0, via Pixnio.
34
+ ```
@@ -0,0 +1,4 @@
1
+ ---
2
+ typography:
3
+ preset: text-forward
4
+ ---
@@ -0,0 +1,34 @@
1
+ ---
2
+ title: Statement
3
+ description: A stronger typography preset for short declarative content.
4
+ navigation:
5
+ label: Statement
6
+ ---
7
+
8
+ ## Statement {#statement}
9
+
10
+ This route uses the `statement` preset. Its purpose is to make short,
11
+ declarative sections feel more direct and programmatic. It is intentionally
12
+ stronger than the other presets and should usually be used sparingly.
13
+
14
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec id elit non mi
15
+ porta gravida at eget metus. Integer posuere erat a ante venenatis dapibus
16
+ posuere velit aliquet.
17
+
18
+ ### Short emphasis
19
+
20
+ Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Aenean
21
+ lacinia bibendum nulla sed consectetur. Maecenas sed diam eget risus varius
22
+ blandit sit amet non magna.
23
+
24
+ ```norna-image-stack
25
+ - image: sheepdog-grass.jpg
26
+ alt: A sheepdog puppy sitting in green grass.
27
+ caption: Sheepdog puppy in grass. Photo by Bicanski, CC0, via Pixnio.
28
+ - image: sheepdog-wide.jpg
29
+ alt: A sheepdog puppy lying outdoors.
30
+ caption: Sheepdog puppy outdoors. Photo by Bicanski, CC0, via Pixnio.
31
+ - image: black-pedigree.jpg
32
+ alt: A black dog standing in grass.
33
+ caption: Black pedigree dog portrait. Photo by Bicanski, CC0, via Pixnio.
34
+ ```
@@ -0,0 +1,4 @@
1
+ ---
2
+ typography:
3
+ preset: statement
4
+ ---
@@ -0,0 +1,14 @@
1
+ ---
2
+ navigation:
3
+ brand: Typography Presets
4
+ footer:
5
+ copyrightMessage: Dog photos by Bicanski, CC0, via Pixnio.
6
+ buildInfo:
7
+ enabled: true
8
+ text: Built
9
+ dateTimeFormat:
10
+ locale: en-GB
11
+ timeZone: UTC
12
+ dateStyle: short
13
+ timeStyle: short
14
+ ---
@@ -0,0 +1,43 @@
1
+ ---
2
+ layout:
3
+ density: normal
4
+ pageWidth: 1320px
5
+ gutter:
6
+ desktop: clamp(1.25rem, 4vw, 3rem)
7
+ mobile: 1rem
8
+ spacing:
9
+ firstSectionTop:
10
+ desktop: clamp(1.5rem, 3vw, 2.5rem)
11
+ mobile: 1rem
12
+ sectionGap:
13
+ desktop: clamp(2rem, 4vw, 3.5rem)
14
+ mobile: 2rem
15
+ finalSectionBottom:
16
+ desktop: clamp(2rem, 4vw, 3.5rem)
17
+ mobile: 2rem
18
+ headingToBlock:
19
+ desktop: 0.75em
20
+ mobile: 0.7em
21
+ blockGap:
22
+ desktop: 1.5em
23
+ mobile: 1.25em
24
+ imageGap:
25
+ desktop: clamp(1.25rem, 3vw, 2.25rem)
26
+ mobile: 1.5rem
27
+ gallery:
28
+ width: 70vw
29
+ maxAvailableWidthPercent:
30
+ desktop: 100
31
+ mobile: 100
32
+ maxAvailableHeightPercent:
33
+ desktop: 72
34
+ mobile: 68
35
+ typography:
36
+ fontFamily: "Arial, 'Helvetica Neue', Helvetica, sans-serif"
37
+ preset: quiet-gallery
38
+ rhythm: normal
39
+ presentation:
40
+ palette: paper # Alternatives: dark, light
41
+ sectionSurfaces:
42
+ mode: cycle
43
+ ---
@@ -19,19 +19,6 @@ export default {
19
19
  gallery: 'Images',
20
20
  },
21
21
  },
22
- footer: {
23
- copyrightMessage: '(c) Fixture Artist.',
24
- buildInfo: {
25
- enabled: true,
26
- text: 'Built',
27
- dateTimeFormat: {
28
- locale: 'en-GB',
29
- timeZone: 'UTC',
30
- dateStyle: 'short',
31
- timeStyle: 'short',
32
- },
33
- },
34
- },
35
22
  github: {
36
23
  repo: 'owner/fixture-site',
37
24
  branch: 'main',
@@ -0,0 +1,12 @@
1
+ ---
2
+ footer:
3
+ copyrightMessage: (c) Fixture Artist.
4
+ buildInfo:
5
+ enabled: true
6
+ text: Built
7
+ dateTimeFormat:
8
+ locale: en-GB
9
+ timeZone: UTC
10
+ dateStyle: short
11
+ timeStyle: short
12
+ ---
@@ -6,6 +6,6 @@ gallery:
6
6
  typography:
7
7
  fontFamily: "Arial, 'Helvetica Neue', Helvetica, sans-serif"
8
8
  preset: quiet-gallery
9
- frame:
10
- colors: presentation
9
+ presentation:
10
+ palette: dark
11
11
  ---
@@ -2,10 +2,7 @@
2
2
  title: Content Model v2 Fixture
3
3
  description: Internal fixture for Markdown-authoritative Norna content.
4
4
  sections:
5
- intro:
6
- presentation:
7
- typography:
8
- preset: statement
5
+ intro: {}
9
6
  timed:
10
7
  visible:
11
8
  from: "2026-01-01"
@@ -15,7 +12,7 @@ sections:
15
12
  ## Intro {#intro}
16
13
 
17
14
  This fixture validates that Markdown defines sections and that section metadata
18
- is optional. It also checks [inline styles]{.highlight}.
15
+ is optional. It also checks **inline emphasis**.
19
16
 
20
17
  ```norna-image-stack
21
18
  - image: hero.jpg
@@ -3,7 +3,5 @@ typography:
3
3
  preset: text-forward
4
4
  rhythm: normal
5
5
  presentation:
6
- inlineStyles:
7
- highlight:
8
- color: "#b3261e"
6
+ palette: dark
9
7
  ---
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@janga/norna",
3
3
  "description": "A command-line publishing workflow for simple static sites.",
4
4
  "type": "module",
5
- "version": "0.7.8",
5
+ "version": "0.7.10",
6
6
  "license": "GPL-3.0-only",
7
7
  "repository": {
8
8
  "type": "git",
@@ -85,7 +85,8 @@
85
85
  },
86
86
  "dependencies": {
87
87
  "astro": "^7.1.3",
88
- "embla-carousel": "^8.6.0"
88
+ "embla-carousel": "^8.6.0",
89
+ "satteri": "^0.9.1"
89
90
  },
90
91
  "devDependencies": {
91
92
  "@playwright/test": "^1.61.1"
@@ -1,4 +1,7 @@
1
- import { siteConfigLabel } from './lib/site-paths.mjs';
1
+ import { siteConfigLabel, sitePublicLabel } from './lib/site-paths.mjs';
2
+ import { getLogoAssets } from './lib/logo-assets.mjs';
3
+ import { readSitewideConfig } from './lib/sitewide-config.mjs';
4
+ import { readThemeConfig, validateRouteThemeFiles } from './lib/theme-config.mjs';
2
5
 
3
6
  const formatErrorMessage = (error) => {
4
7
  if (error instanceof SyntaxError) {
@@ -17,6 +20,24 @@ const formatErrorMessage = (error) => {
17
20
 
18
21
  try {
19
22
  const { projectConfig } = await import('./lib/project-config.mjs');
23
+ await readThemeConfig();
24
+ const sitewideConfig = await readSitewideConfig();
25
+ await validateRouteThemeFiles();
26
+ const logoAssets = getLogoAssets();
27
+
28
+ if (logoAssets.length > 1) {
29
+ throw new Error([
30
+ `Found multiple logo files in ${sitePublicLabel}. Keep exactly one of logo.svg, logo.png, logo.jpg, or logo.jpeg.`,
31
+ ...logoAssets.map(({ filename }) => `- ${sitePublicLabel}/${filename}`),
32
+ ].join('\n'));
33
+ }
34
+
35
+ if (logoAssets.length === 0) {
36
+ console.warn(`Warning: No logo file found in ${sitePublicLabel}. Norna will use sitewide navigation.brand or the homepage title as the navigation label.`);
37
+ console.warn(`Add exactly one of ${sitePublicLabel}/logo.svg, logo.png, logo.jpg, or logo.jpeg when the site should have a logo.`);
38
+ } else if (sitewideConfig.navigation?.brand) {
39
+ console.warn('Warning: Both sitewide navigation.brand and a logo file are configured. The logo is used; navigation.brand is only the text fallback.');
40
+ }
20
41
 
21
42
  console.log('Config check passed.');
22
43
  console.log(`Site URL: ${projectConfig.site.url}`);
@@ -20,9 +20,37 @@ const stripInlineComment = (value) => {
20
20
  return value;
21
21
  };
22
22
 
23
+ const splitInlineList = (value) => {
24
+ const entries = [];
25
+ let entryStart = 0;
26
+ let quote = null;
27
+
28
+ for (let index = 0; index < value.length; index += 1) {
29
+ const character = value[index];
30
+ const previous = value[index - 1];
31
+
32
+ if ((character === '"' || character === "'") && previous !== '\\') {
33
+ quote = quote === character ? null : quote ?? character;
34
+ continue;
35
+ }
36
+
37
+ if (character === ',' && quote === null) {
38
+ entries.push(value.slice(entryStart, index).trim());
39
+ entryStart = index + 1;
40
+ }
41
+ }
42
+
43
+ entries.push(value.slice(entryStart).trim());
44
+ return entries.filter(Boolean);
45
+ };
46
+
23
47
  const parseScalar = (rawValue) => {
24
48
  const value = stripInlineComment(rawValue.trim());
25
49
 
50
+ if (value.startsWith('[') && value.endsWith(']')) {
51
+ const listValue = value.slice(1, -1).trim();
52
+ return listValue ? splitInlineList(listValue).map(parseScalar) : [];
53
+ }
26
54
  if (value === 'true') return true;
27
55
  if (value === 'false') return false;
28
56
  if (/^-?\d+(?:\.\d+)?$/.test(value)) return Number(value);
@@ -0,0 +1,35 @@
1
+ import { readdirSync } from 'node:fs';
2
+ import { sitePublicDir, sitePublicLabel } from './site-paths.mjs';
3
+
4
+ const logoFilePattern = /^logo\.(svg|png|jpe?g)$/i;
5
+
6
+ export const getLogoAssets = () => {
7
+ let entries;
8
+
9
+ try {
10
+ entries = readdirSync(sitePublicDir, { withFileTypes: true });
11
+ } catch (error) {
12
+ if (error?.code === 'ENOENT') return [];
13
+ throw error;
14
+ }
15
+
16
+ return entries
17
+ .filter((entry) => entry.isFile() && logoFilePattern.test(entry.name))
18
+ .map((entry) => ({
19
+ filename: entry.name,
20
+ href: `/${entry.name}`,
21
+ }));
22
+ };
23
+
24
+ export const getLogoAsset = () => {
25
+ const assets = getLogoAssets();
26
+
27
+ if (assets.length > 1) {
28
+ throw new Error([
29
+ `Found multiple logo files in ${sitePublicLabel}. Keep exactly one of logo.svg, logo.png, logo.jpg, or logo.jpeg.`,
30
+ ...assets.map(({ filename }) => `- ${sitePublicLabel}/${filename}`),
31
+ ].join('\n'));
32
+ }
33
+
34
+ return assets[0] ?? null;
35
+ };
@@ -588,6 +588,172 @@ export const splitNornaMarkdownBlockMarkers = (html, options = {}) => {
588
588
 
589
589
  const normalizeBlockSource = (source) => source.replace(/\r\n?/g, '\n').trim();
590
590
 
591
+ const inlineNoteReferenceRegex = /\{note-ref\}/g;
592
+ const inlineNoteDeclarationRegex = /^\s*\{note:\s*(.*)\}\s*$/;
593
+ const inlineNoteDeclarationStartRegex = /\{note(?:\s*:|\s*\})/;
594
+
595
+ const maskInlineCodeAndComments = (source) => source.replace(
596
+ /<!--[\s\S]*?-->|(`+)([\s\S]*?)\1/g,
597
+ (match) => match.replace(/[^\r\n]/g, ' '),
598
+ );
599
+
600
+ const countInlineNoteReferences = (line) => Array.from(line.matchAll(inlineNoteReferenceRegex)).length;
601
+
602
+ const formatInlineNoteError = (message, options, line) => ({
603
+ blockType: 'inline-note',
604
+ line,
605
+ message: failMessage(message, { ...options, line }),
606
+ });
607
+
608
+ const getInlineNoteErrorMessage = () => 'Write the note on its own line as "{note: Your explanatory text.}".';
609
+
610
+ /**
611
+ * Finds the deliberately small inline-note syntax while ignoring fenced and
612
+ * inline code. Notes are paired by position, so no user-authored IDs are
613
+ * needed. The caller decides whether diagnostics should be fatal.
614
+ */
615
+ export const extractInlineNoteDiagnostics = (markdown, options = {}) => {
616
+ const lines = normalizeLines(markdown);
617
+ const maskedLines = maskInlineCodeAndComments(maskFencedCodeBlocks(markdown)).split('\n');
618
+ const notes = [];
619
+ const errors = [];
620
+ let paragraph = null;
621
+ let pendingParagraph = null;
622
+ let noteJustSeen = false;
623
+
624
+ const addError = (message, line) => errors.push(formatInlineNoteError(message, options, line));
625
+
626
+ const finishParagraph = () => {
627
+ if (!paragraph) return;
628
+ if (paragraph.referenceCount > 0) {
629
+ pendingParagraph = paragraph;
630
+ }
631
+ paragraph = null;
632
+ };
633
+
634
+ const reportMissingNote = () => {
635
+ if (!pendingParagraph) return;
636
+ addError(
637
+ `Paragraph on line ${pendingParagraph.referenceLine} contains "{note-ref}" but has no following "{note: ...}".`,
638
+ pendingParagraph.referenceLine,
639
+ );
640
+ pendingParagraph = null;
641
+ };
642
+
643
+ const attachNote = (noteMatch, lineNumber) => {
644
+ const text = noteMatch[1].trim();
645
+ if (!text) {
646
+ addError(`The note on line ${lineNumber} is empty. ${getInlineNoteErrorMessage()}`, lineNumber);
647
+ pendingParagraph = null;
648
+ return;
649
+ }
650
+
651
+ if (/!\[[^\]]*\]\([^)]*\)|<img\b/i.test(text)) {
652
+ addError('Inline notes cannot contain images. Use a Norna image block with a caption instead.', lineNumber);
653
+ pendingParagraph = null;
654
+ return;
655
+ }
656
+
657
+ notes.push({
658
+ markdown: text,
659
+ referenceLine: pendingParagraph.referenceLine,
660
+ noteLine: lineNumber,
661
+ });
662
+ pendingParagraph = null;
663
+ noteJustSeen = true;
664
+ };
665
+
666
+ for (let index = 0; index < lines.length; index += 1) {
667
+ const line = lines[index] ?? '';
668
+ const maskedLine = maskedLines[index] ?? line;
669
+ const lineNumber = (options.lineOffset ?? 0) + index + 1;
670
+ const fence = getFenceInfo(line);
671
+
672
+ if (fence && fence.length >= 3) {
673
+ finishParagraph();
674
+ reportMissingNote();
675
+ let closingIndex = -1;
676
+ for (let candidateIndex = index + 1; candidateIndex < lines.length; candidateIndex += 1) {
677
+ if (getFenceCloseInfo(lines[candidateIndex], fence)) {
678
+ closingIndex = candidateIndex;
679
+ break;
680
+ }
681
+ }
682
+ index = closingIndex === -1 ? lines.length : closingIndex;
683
+ noteJustSeen = false;
684
+ continue;
685
+ }
686
+
687
+ const trimmed = line.trim();
688
+ const maskedTrimmed = maskedLine.trim();
689
+ const noteMatch = trimmed.match(inlineNoteDeclarationRegex);
690
+ const looksLikeNoteDeclaration = inlineNoteDeclarationStartRegex.test(maskedTrimmed);
691
+ const referenceCount = countInlineNoteReferences(maskedLine);
692
+ const isHeading = /^#{1,6}(?:\s|$)/.test(trimmed);
693
+
694
+ if (!trimmed) {
695
+ finishParagraph();
696
+ noteJustSeen = false;
697
+ continue;
698
+ }
699
+
700
+ if (pendingParagraph) {
701
+ if (noteMatch) {
702
+ attachNote(noteMatch, lineNumber);
703
+ continue;
704
+ }
705
+
706
+ if (looksLikeNoteDeclaration) {
707
+ addError(`Invalid note syntax on line ${lineNumber}. ${getInlineNoteErrorMessage()}`, lineNumber);
708
+ pendingParagraph = null;
709
+ } else {
710
+ reportMissingNote();
711
+ }
712
+ }
713
+
714
+ if (noteJustSeen) {
715
+ addError(`Add a blank line after the note on the previous line before starting more text.`, lineNumber);
716
+ noteJustSeen = false;
717
+ }
718
+
719
+ if (noteMatch || looksLikeNoteDeclaration) {
720
+ addError(
721
+ noteMatch
722
+ ? `A "{note: ...}" requires a preceding paragraph containing "{note-ref}".`
723
+ : `Invalid note syntax on line ${lineNumber}. ${getInlineNoteErrorMessage()}`,
724
+ lineNumber,
725
+ );
726
+ continue;
727
+ }
728
+
729
+ if (isHeading) {
730
+ if (referenceCount > 0) {
731
+ addError('Place "{note-ref}" in a paragraph, not in a heading.', lineNumber);
732
+ }
733
+ finishParagraph();
734
+ reportMissingNote();
735
+ continue;
736
+ }
737
+
738
+ if (!paragraph) {
739
+ paragraph = {
740
+ referenceCount: 0,
741
+ referenceLine: lineNumber,
742
+ };
743
+ }
744
+
745
+ paragraph.referenceCount += referenceCount;
746
+ if (referenceCount > 0 && paragraph.referenceCount > 1) {
747
+ addError('A paragraph may contain only one "{note-ref}".', lineNumber);
748
+ }
749
+ }
750
+
751
+ finishParagraph();
752
+ reportMissingNote();
753
+
754
+ return { notes, errors };
755
+ };
756
+
591
757
  export const splitNornaRenderedCodeBlocks = (html, rawBlocks, options = {}) => {
592
758
  const blocks = [];
593
759
  const renderedCodeRegex = /<pre\b[^>]*>\s*<code\b[^>]*>([\s\S]*?)<\/code>\s*<\/pre>/g;