@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
@@ -1,71 +1,129 @@
1
1
  import {
2
2
  defaultTypography,
3
3
  resolveTypographyConfig,
4
- resolveTypographyOverride,
5
4
  } from './typography.mjs';
6
5
 
7
- export const fallbackPresentationColors = Object.freeze({
8
- backgroundColor: 'transparent',
9
- textColor: 'var(--color-text)',
6
+ export const presentationPaletteNames = ['dark', 'light', 'paper'];
7
+
8
+ const presentationPalettes = Object.freeze({
9
+ dark: {
10
+ colorScheme: 'dark',
11
+ page: { backgroundColor: '#000000', textColor: '#f2eee6' },
12
+ surfaces: {
13
+ base: { backgroundColor: '#000000', textColor: '#f2eee6' },
14
+ soft: { backgroundColor: '#171717', textColor: '#f2eee6' },
15
+ emphasis: { backgroundColor: '#252525', textColor: '#f2eee6' },
16
+ },
17
+ frame: { backgroundColor: '#000000', textColor: '#f2eee6' },
18
+ css: {
19
+ surface: '#101010',
20
+ muted: '#aaa49a',
21
+ soft: '#302f2c',
22
+ accent: '#d8d2c8',
23
+ border: 'rgb(255 255 255 / 14%)',
24
+ navBackground: 'rgb(0 0 0 / 90%)',
25
+ navSeparator: 'rgb(255 255 255 / 28%)',
26
+ },
27
+ },
28
+ light: {
29
+ colorScheme: 'light',
30
+ page: { backgroundColor: '#ffffff', textColor: '#17201d' },
31
+ surfaces: {
32
+ base: { backgroundColor: '#ffffff', textColor: '#17201d' },
33
+ soft: { backgroundColor: '#f1f4f2', textColor: '#17201d' },
34
+ emphasis: { backgroundColor: '#dde7e1', textColor: '#17201d' },
35
+ },
36
+ frame: { backgroundColor: '#ffffff', textColor: '#17201d' },
37
+ css: {
38
+ surface: '#f7f8f7',
39
+ muted: '#5e655f',
40
+ soft: '#d9dfda',
41
+ accent: '#38645a',
42
+ border: 'rgb(0 0 0 / 14%)',
43
+ navBackground: 'rgb(255 255 255 / 92%)',
44
+ navSeparator: 'rgb(0 0 0 / 20%)',
45
+ },
46
+ },
47
+ paper: {
48
+ colorScheme: 'light',
49
+ page: { backgroundColor: '#f8f5ee', textColor: '#272522' },
50
+ surfaces: {
51
+ base: { backgroundColor: '#f8f5ee', textColor: '#272522' },
52
+ soft: { backgroundColor: '#ebe5d9', textColor: '#272522' },
53
+ emphasis: { backgroundColor: '#ded4c5', textColor: '#272522' },
54
+ },
55
+ frame: { backgroundColor: '#f8f5ee', textColor: '#272522' },
56
+ css: {
57
+ surface: '#f0ebe1',
58
+ muted: '#746e63',
59
+ soft: '#d8d0c4',
60
+ accent: '#685a43',
61
+ border: 'rgb(39 37 34 / 18%)',
62
+ navBackground: 'rgb(248 245 238 / 92%)',
63
+ navSeparator: 'rgb(39 37 34 / 24%)',
64
+ },
65
+ },
10
66
  });
11
67
 
12
68
  const normalizePresentation = (presentation) => presentation ?? {};
13
69
 
70
+ export const getPresentationPalette = (paletteName = 'dark') => {
71
+ const palette = presentationPalettes[paletteName];
72
+ if (!palette) {
73
+ throw new Error(`Unknown presentation palette: ${paletteName}. Use one of: ${presentationPaletteNames.join(', ')}`);
74
+ }
75
+
76
+ return palette;
77
+ };
78
+
79
+ export const getPresentationCssVariables = (presentation) => {
80
+ const { palette } = presentation;
81
+ return {
82
+ '--color-page': palette.page.backgroundColor,
83
+ '--color-text': palette.page.textColor,
84
+ '--color-surface': palette.css.surface,
85
+ '--color-muted': palette.css.muted,
86
+ '--color-soft': palette.css.soft,
87
+ '--color-accent': palette.css.accent,
88
+ '--color-border': palette.css.border,
89
+ '--color-nav-background': palette.css.navBackground,
90
+ '--color-nav-separator': palette.css.navSeparator,
91
+ '--color-scheme': palette.colorScheme,
92
+ };
93
+ };
94
+
14
95
  export const resolveThemePresentation = (theme) => {
15
96
  const normalizedTheme = theme ?? {};
16
97
  const normalizedThemePresentation = normalizePresentation(normalizedTheme.presentation);
98
+ const paletteName = normalizedThemePresentation.palette ?? 'dark';
99
+ const palette = getPresentationPalette(paletteName);
17
100
 
18
101
  return {
19
- backgroundColor: normalizedThemePresentation.backgroundColor ?? fallbackPresentationColors.backgroundColor,
20
- textColor: normalizedThemePresentation.textColor ?? fallbackPresentationColors.textColor,
21
- inlineStyles: normalizedThemePresentation.inlineStyles ?? {},
102
+ paletteName,
103
+ palette,
104
+ sectionSurfaces: {
105
+ mode: normalizedThemePresentation.sectionSurfaces?.mode ?? 'none',
106
+ sequence: normalizedThemePresentation.sectionSurfaces?.sequence ?? ['base', 'soft', 'emphasis'],
107
+ },
22
108
  typography: resolveTypographyConfig(normalizedTheme.typography ?? defaultTypography),
23
109
  };
24
110
  };
25
111
 
26
- export const resolvePagePresentation = (theme, pagePresentation) => {
112
+ export const resolvePagePresentation = (theme) => {
27
113
  const resolvedThemePresentation = resolveThemePresentation(theme);
28
- const normalizedPagePresentation = normalizePresentation(pagePresentation);
29
114
 
30
- return {
31
- backgroundColor: normalizedPagePresentation.backgroundColor ?? resolvedThemePresentation.backgroundColor,
32
- textColor: normalizedPagePresentation.textColor ?? resolvedThemePresentation.textColor,
33
- inlineStyles: resolvedThemePresentation.inlineStyles,
34
- typography: resolveTypographyOverride(
35
- resolvedThemePresentation.typography,
36
- normalizedPagePresentation.typography,
37
- ),
38
- };
115
+ return resolvedThemePresentation;
39
116
  };
40
117
 
41
- const getColorsFromPresentation = (presentation) => ({
42
- backgroundColor: presentation.backgroundColor,
43
- textColor: presentation.textColor,
44
- });
45
-
46
- const resolveColorSource = ({ colors, themeFrameColors, pagePresentation }) => {
47
- if (!colors || colors === 'theme') {
48
- return themeFrameColors;
49
- }
50
-
51
- if (colors === 'presentation') {
52
- return getColorsFromPresentation(pagePresentation);
53
- }
118
+ export const resolveSectionSurface = (pagePresentation, sectionIndex) => {
119
+ const surfaceName = pagePresentation.sectionSurfaces.mode === 'cycle'
120
+ ? pagePresentation.sectionSurfaces.sequence[sectionIndex % pagePresentation.sectionSurfaces.sequence.length]
121
+ : 'base';
54
122
 
55
- return colors;
123
+ return {
124
+ name: surfaceName,
125
+ ...pagePresentation.palette.surfaces[surfaceName],
126
+ };
56
127
  };
57
128
 
58
- export const resolveFrameColors = ({ theme, pagePresentation, pageFrame }) => {
59
- const resolvedThemePresentation = resolveThemePresentation(theme);
60
- const themeFrameColors = resolveColorSource({
61
- colors: theme?.frame?.colors ?? 'presentation',
62
- themeFrameColors: getColorsFromPresentation(resolvedThemePresentation),
63
- pagePresentation: resolvedThemePresentation,
64
- });
65
-
66
- return resolveColorSource({
67
- colors: pageFrame?.colors ?? 'theme',
68
- themeFrameColors,
69
- pagePresentation,
70
- });
71
- };
129
+ export const resolveFrameColors = ({ theme }) => resolveThemePresentation(theme).palette.frame;
@@ -37,20 +37,6 @@ const readString = (object, key, path, sourceLabel = siteConfigLabel) => {
37
37
  return value.trim();
38
38
  };
39
39
 
40
- const readOptionalString = (object, key, path, sourceLabel = siteConfigLabel) => {
41
- const value = object[key];
42
-
43
- if (value === undefined || value === null || value === '') {
44
- return null;
45
- }
46
-
47
- if (typeof value !== 'string' || value.trim() === '') {
48
- throw new Error(`${path}.${key} must be a non-empty string when set in ${sourceLabel}.`);
49
- }
50
-
51
- return value.trim();
52
- };
53
-
54
40
  const readFontFamily = (object, key, path, fallback, sourceLabel = siteConfigLabel) => {
55
41
  const value = object[key] ?? fallback;
56
42
 
@@ -242,58 +228,19 @@ const readLocale = (rawLocale) => {
242
228
  lang: lang.trim(),
243
229
  labels: Object.freeze({
244
230
  closeMenu: readString({ closeMenu: labels.closeMenu ?? 'Close menu' }, 'closeMenu', 'locale.labels'),
231
+ dismissBanner: readString({ dismissBanner: labels.dismissBanner ?? 'Dismiss notice' }, 'dismissBanner', 'locale.labels'),
245
232
  skipToContent: readString({ skipToContent: labels.skipToContent ?? 'Skip to content' }, 'skipToContent', 'locale.labels'),
246
233
  sectionNavigation: readString({ sectionNavigation: labels.sectionNavigation ?? 'Sections' }, 'sectionNavigation', 'locale.labels'),
247
234
  gallery: readString({ gallery: labels.gallery ?? 'Images' }, 'gallery', 'locale.labels'),
235
+ note: readString({ note: labels.note ?? 'Note' }, 'note', 'locale.labels'),
248
236
  menu: readString({ menu: labels.menu ?? 'Menu' }, 'menu', 'locale.labels'),
249
237
  pageNavigation: readString({ pageNavigation: labels.pageNavigation ?? 'On this page' }, 'pageNavigation', 'locale.labels'),
250
238
  siteNavigation: readString({ siteNavigation: labels.siteNavigation ?? 'Pages' }, 'siteNavigation', 'locale.labels'),
239
+ siteBanners: readString({ siteBanners: labels.siteBanners ?? 'Site notices' }, 'siteBanners', 'locale.labels'),
251
240
  }),
252
241
  });
253
242
  };
254
243
 
255
- const readDateTimeFormat = (object, path) => {
256
- const dateTimeFormat = assertObject(object, path);
257
- const locale = readString(dateTimeFormat, 'locale', path);
258
- const timeZone = readString(dateTimeFormat, 'timeZone', path);
259
- const dateStyle = readString(dateTimeFormat, 'dateStyle', path);
260
- const timeStyle = readString(dateTimeFormat, 'timeStyle', path);
261
-
262
- try {
263
- new Intl.DateTimeFormat(locale, {
264
- dateStyle,
265
- timeStyle,
266
- timeZone,
267
- });
268
- } catch (error) {
269
- const message = error instanceof Error ? error.message : String(error);
270
- throw new Error(`${path} must be a valid Intl.DateTimeFormat configuration in ${siteConfigLabel}: ${message}`);
271
- }
272
-
273
- return Object.freeze({
274
- dateStyle,
275
- locale,
276
- timeStyle,
277
- timeZone,
278
- });
279
- };
280
-
281
- const readBuildInfo = (footer) => {
282
- const value = footer.buildInfo;
283
-
284
- if (value === undefined || value === null || value === false) {
285
- return null;
286
- }
287
-
288
- const buildInfo = assertObject(value, 'footer.buildInfo');
289
-
290
- return Object.freeze({
291
- enabled: readBoolean(buildInfo, 'enabled', 'footer.buildInfo', true),
292
- text: readString(buildInfo, 'text', 'footer.buildInfo'),
293
- dateTimeFormat: readDateTimeFormat(buildInfo.dateTimeFormat, 'footer.buildInfo.dateTimeFormat'),
294
- });
295
- };
296
-
297
244
  const rawConfig = assertObject(siteConfig, 'default export');
298
245
  const misplacedThemeKeys = ['layout', 'gallery', 'typography'].filter((key) => key in rawConfig);
299
246
  if (misplacedThemeKeys.length > 0) {
@@ -302,13 +249,8 @@ if (misplacedThemeKeys.length > 0) {
302
249
 
303
250
  const rawTheme = assertObject(themeConfig, 'theme frontmatter', siteThemeLabel);
304
251
  const rawSite = assertObject(rawConfig.site, 'site');
305
- const rawLayout = assertObject(rawTheme.layout ?? {}, 'layout', siteThemeLabel);
306
- const rawLayoutSpacing = assertObject(rawLayout.spacing ?? {}, 'layout.spacing', siteThemeLabel);
307
- const rawGallery = assertObject(rawTheme.gallery ?? {}, 'gallery', siteThemeLabel);
308
- const rawTypography = assertObject(rawTheme.typography ?? {}, 'typography', siteThemeLabel);
309
252
  const rawNavigation = assertObject(rawConfig.navigation ?? {}, 'navigation');
310
253
  const rawLocale = rawConfig.locale ?? {};
311
- const rawFooter = assertObject(rawConfig.footer ?? {}, 'footer');
312
254
  const rawGithub = assertObject(rawConfig.github, 'github');
313
255
  const rawDeploy = assertObject(rawConfig.deploy ?? {}, 'deploy');
314
256
  const rawDeployWatch = assertObject(rawDeploy.watch ?? {}, 'deploy.watch');
@@ -317,9 +259,9 @@ const defaultFontFamily = "Arial, 'Helvetica Neue', Helvetica, sans-serif";
317
259
  const layoutDensityNames = ['compact', 'normal', 'airy'];
318
260
  const layoutDensityProfiles = Object.freeze({
319
261
  compact: Object.freeze({
320
- bodyToImages: Object.freeze({
321
- desktop: '1rem',
322
- mobile: '0.9rem',
262
+ blockGap: Object.freeze({
263
+ desktop: '1.25em',
264
+ mobile: '1.1em',
323
265
  }),
324
266
  finalSectionBottom: Object.freeze({
325
267
  desktop: 'clamp(1.2rem, 2.4vw, 2.25rem)',
@@ -329,6 +271,10 @@ const layoutDensityProfiles = Object.freeze({
329
271
  desktop: 'clamp(1rem, 2vw, 1.75rem)',
330
272
  mobile: '1rem',
331
273
  }),
274
+ headingToBlock: Object.freeze({
275
+ desktop: '0.65em',
276
+ mobile: '0.6em',
277
+ }),
332
278
  imageGap: Object.freeze({
333
279
  desktop: 'clamp(1rem, 2vw, 1.5rem)',
334
280
  mobile: '1.25rem',
@@ -339,9 +285,9 @@ const layoutDensityProfiles = Object.freeze({
339
285
  }),
340
286
  }),
341
287
  normal: Object.freeze({
342
- bodyToImages: Object.freeze({
343
- desktop: '1.25rem',
344
- mobile: '1rem',
288
+ blockGap: Object.freeze({
289
+ desktop: '1.5em',
290
+ mobile: '1.25em',
345
291
  }),
346
292
  finalSectionBottom: Object.freeze({
347
293
  desktop: 'clamp(1.4rem, 3vw, 2.75rem)',
@@ -351,6 +297,10 @@ const layoutDensityProfiles = Object.freeze({
351
297
  desktop: 'clamp(1.25rem, 3vw, 2.5rem)',
352
298
  mobile: '1.25rem',
353
299
  }),
300
+ headingToBlock: Object.freeze({
301
+ desktop: '0.75em',
302
+ mobile: '0.7em',
303
+ }),
354
304
  imageGap: Object.freeze({
355
305
  desktop: 'clamp(1.25rem, 2.8vw, 2rem)',
356
306
  mobile: '1.5rem',
@@ -361,9 +311,9 @@ const layoutDensityProfiles = Object.freeze({
361
311
  }),
362
312
  }),
363
313
  airy: Object.freeze({
364
- bodyToImages: Object.freeze({
365
- desktop: '1.5rem',
366
- mobile: '1.25rem',
314
+ blockGap: Object.freeze({
315
+ desktop: '1.75em',
316
+ mobile: '1.5em',
367
317
  }),
368
318
  finalSectionBottom: Object.freeze({
369
319
  desktop: 'clamp(2.25rem, 5vw, 4.5rem)',
@@ -373,6 +323,10 @@ const layoutDensityProfiles = Object.freeze({
373
323
  desktop: 'clamp(2rem, 5vw, 4rem)',
374
324
  mobile: '1.5rem',
375
325
  }),
326
+ headingToBlock: Object.freeze({
327
+ desktop: '0.9em',
328
+ mobile: '0.8em',
329
+ }),
376
330
  imageGap: Object.freeze({
377
331
  desktop: 'clamp(1.5rem, 3.5vw, 2.75rem)',
378
332
  mobile: '2rem',
@@ -383,51 +337,59 @@ const layoutDensityProfiles = Object.freeze({
383
337
  }),
384
338
  }),
385
339
  });
386
- const layoutDensity = readEnum(rawLayout, 'density', 'layout', layoutDensityNames, 'normal', siteThemeLabel);
387
- const layoutSpacingDefaults = layoutDensityProfiles[layoutDensity];
340
+ export const resolveThemeVisualConfig = (theme, sourceLabel = siteThemeLabel) => {
341
+ const rawThemeConfig = assertObject(theme ?? {}, 'theme frontmatter', sourceLabel);
342
+ const rawLayoutConfig = assertObject(rawThemeConfig.layout ?? {}, 'layout', sourceLabel);
343
+ const rawLayoutSpacingConfig = assertObject(rawLayoutConfig.spacing ?? {}, 'layout.spacing', sourceLabel);
344
+ const rawGalleryConfig = assertObject(rawThemeConfig.gallery ?? {}, 'gallery', sourceLabel);
345
+ const rawTypographyConfig = assertObject(rawThemeConfig.typography ?? {}, 'typography', sourceLabel);
346
+ const resolvedLayoutDensity = readEnum(rawLayoutConfig, 'density', 'layout', layoutDensityNames, 'normal', sourceLabel);
347
+ const resolvedLayoutSpacingDefaults = layoutDensityProfiles[resolvedLayoutDensity];
348
+
349
+ return Object.freeze({
350
+ layout: Object.freeze({
351
+ density: resolvedLayoutDensity,
352
+ gutter: readResponsiveCssLength(rawLayoutConfig, 'gutter', 'layout', Object.freeze({
353
+ desktop: 'clamp(1.25rem, 4vw, 3rem)',
354
+ mobile: '1rem',
355
+ }), sourceLabel),
356
+ pageWidth: readCssLength(rawLayoutConfig, 'pageWidth', 'layout', '1180px', sourceLabel),
357
+ spacing: Object.freeze({
358
+ blockGap: readResponsiveCssLength(rawLayoutSpacingConfig, 'blockGap', 'layout.spacing', resolvedLayoutSpacingDefaults.blockGap, sourceLabel),
359
+ finalSectionBottom: readResponsiveCssLength(rawLayoutSpacingConfig, 'finalSectionBottom', 'layout.spacing', resolvedLayoutSpacingDefaults.finalSectionBottom, sourceLabel),
360
+ firstSectionTop: readResponsiveCssLength(rawLayoutSpacingConfig, 'firstSectionTop', 'layout.spacing', resolvedLayoutSpacingDefaults.firstSectionTop, sourceLabel),
361
+ headingToBlock: readResponsiveCssLength(rawLayoutSpacingConfig, 'headingToBlock', 'layout.spacing', resolvedLayoutSpacingDefaults.headingToBlock, sourceLabel),
362
+ imageGap: readResponsiveCssLength(rawLayoutSpacingConfig, 'imageGap', 'layout.spacing', resolvedLayoutSpacingDefaults.imageGap, sourceLabel),
363
+ sectionGap: readResponsiveCssLength(rawLayoutSpacingConfig, 'sectionGap', 'layout.spacing', resolvedLayoutSpacingDefaults.sectionGap, sourceLabel),
364
+ }),
365
+ }),
366
+ gallery: Object.freeze({
367
+ maxAvailableHeightPercent: readResponsivePercent(rawGalleryConfig, 'maxAvailableHeightPercent', 'gallery', Object.freeze({
368
+ desktop: 74,
369
+ mobile: 68,
370
+ }), sourceLabel),
371
+ maxAvailableWidthPercent: readResponsivePercent(rawGalleryConfig, 'maxAvailableWidthPercent', 'gallery', Object.freeze({
372
+ desktop: 100,
373
+ mobile: 100,
374
+ }), sourceLabel),
375
+ width: readCssLength(rawGalleryConfig, 'width', 'gallery', '900px', sourceLabel),
376
+ }),
377
+ typography: Object.freeze({
378
+ fontFamily: readFontFamily(rawTypographyConfig, 'fontFamily', 'typography', defaultFontFamily, sourceLabel),
379
+ }),
380
+ });
381
+ };
388
382
 
389
383
  export const projectConfig = Object.freeze({
390
384
  site: Object.freeze({
391
385
  basePath: readBasePath(rawSite, 'basePath', 'site'),
392
386
  url: readUrl(rawSite, 'url', 'site'),
393
387
  }),
394
- layout: Object.freeze({
395
- density: layoutDensity,
396
- gutter: readResponsiveCssLength(rawLayout, 'gutter', 'layout', Object.freeze({
397
- desktop: 'clamp(1.25rem, 4vw, 3rem)',
398
- mobile: '1rem',
399
- }), siteThemeLabel),
400
- pageWidth: readCssLength(rawLayout, 'pageWidth', 'layout', '1180px', siteThemeLabel),
401
- spacing: Object.freeze({
402
- bodyToImages: readResponsiveCssLength(rawLayoutSpacing, 'bodyToImages', 'layout.spacing', layoutSpacingDefaults.bodyToImages, siteThemeLabel),
403
- finalSectionBottom: readResponsiveCssLength(rawLayoutSpacing, 'finalSectionBottom', 'layout.spacing', layoutSpacingDefaults.finalSectionBottom, siteThemeLabel),
404
- firstSectionTop: readResponsiveCssLength(rawLayoutSpacing, 'firstSectionTop', 'layout.spacing', layoutSpacingDefaults.firstSectionTop, siteThemeLabel),
405
- imageGap: readResponsiveCssLength(rawLayoutSpacing, 'imageGap', 'layout.spacing', layoutSpacingDefaults.imageGap, siteThemeLabel),
406
- sectionGap: readResponsiveCssLength(rawLayoutSpacing, 'sectionGap', 'layout.spacing', layoutSpacingDefaults.sectionGap, siteThemeLabel),
407
- }),
408
- }),
409
- gallery: Object.freeze({
410
- maxAvailableHeightPercent: readResponsivePercent(rawGallery, 'maxAvailableHeightPercent', 'gallery', Object.freeze({
411
- desktop: 74,
412
- mobile: 68,
413
- }), siteThemeLabel),
414
- maxAvailableWidthPercent: readResponsivePercent(rawGallery, 'maxAvailableWidthPercent', 'gallery', Object.freeze({
415
- desktop: 100,
416
- mobile: 100,
417
- }), siteThemeLabel),
418
- width: readCssLength(rawGallery, 'width', 'gallery', '900px', siteThemeLabel),
419
- }),
420
- typography: Object.freeze({
421
- fontFamily: readFontFamily(rawTypography, 'fontFamily', 'typography', defaultFontFamily, siteThemeLabel),
422
- }),
388
+ ...resolveThemeVisualConfig(rawTheme, siteThemeLabel),
423
389
  navigation: Object.freeze({
424
390
  smoothScroll: readSmoothScroll(rawNavigation),
425
391
  }),
426
392
  locale: readLocale(rawLocale),
427
- footer: Object.freeze({
428
- buildInfo: readBuildInfo(rawFooter),
429
- copyrightMessage: readOptionalString(rawFooter, 'copyrightMessage', 'footer'),
430
- }),
431
393
  github: Object.freeze({
432
394
  repo: readString(rawGithub, 'repo', 'github'),
433
395
  branch: readString(rawGithub, 'branch', 'github'),
@@ -18,21 +18,20 @@ export const supportedImageExtensions = new Set([...rasterImageExtensions, ...st
18
18
 
19
19
  const h2Regex = /^##\s+.*$/gm;
20
20
  const explicitHeadingIdRegex = /\s*\{#([a-z0-9-]+)\}\s*$/;
21
- const inlineStyleReferenceRegex = /\[[^\]\n]+\]\{\.([a-z][a-z0-9-]*)\}/g;
21
+ const deprecatedInlineStyleReferenceRegex = /\[[^\]\n]+\]\{\.([a-z][a-z0-9-]*)\}/g;
22
22
  const frontmatterDelimiterRegex = /^---\s*$/;
23
- const knownContentTopLevelFrontmatterKeys = new Set(['title', 'description', 'navigation', 'presentation', 'frame', 'sections']);
24
- const knownThemeTopLevelFrontmatterKeys = new Set(['navigation', 'layout', 'gallery', 'typography', 'presentation', 'frame']);
23
+ const knownContentTopLevelFrontmatterKeys = new Set(['title', 'description', 'navigation', 'sections']);
24
+ const knownThemeTopLevelFrontmatterKeys = new Set(['navigation', 'layout', 'gallery', 'typography', 'presentation']);
25
+ const knownRouteThemeTopLevelFrontmatterKeys = new Set(['navigation', 'layout', 'gallery', 'typography', 'presentation']);
26
+ const knownSitewideTopLevelFrontmatterKeys = new Set(['navigation', 'banners', 'footer']);
25
27
  const knownNestedFrontmatterKeys = new Set([
26
28
  'align',
27
29
  'alt',
28
- 'backgroundColor',
29
30
  'body',
30
- 'bodyToImages',
31
+ 'blockGap',
31
32
  'brand',
32
33
  'caption',
33
34
  'carousel',
34
- 'color',
35
- 'colors',
36
35
  'density',
37
36
  'desktop',
38
37
  'fontFamily',
@@ -40,6 +39,7 @@ const knownNestedFrontmatterKeys = new Set([
40
39
  'finalSectionBottom',
41
40
  'gallery',
42
41
  'gutter',
42
+ 'headingToBlock',
43
43
  'h1',
44
44
  'h2',
45
45
  'h3',
@@ -50,8 +50,8 @@ const knownNestedFrontmatterKeys = new Set([
50
50
  'include',
51
51
  'image',
52
52
  'imageGap',
53
- 'inlineStyles',
54
53
  'label',
54
+ 'logo',
55
55
  'lineHeight',
56
56
  'maxAvailableHeightPercent',
57
57
  'maxAvailableWidthPercent',
@@ -60,15 +60,18 @@ const knownNestedFrontmatterKeys = new Set([
60
60
  'overrides',
61
61
  'paragraphSpacing',
62
62
  'pageWidth',
63
+ 'palette',
63
64
  'preset',
64
65
  'presentation',
65
66
  'rhythm',
67
+ 'sequence',
66
68
  'sections',
67
69
  'sectionGap',
70
+ 'sectionSurfaces',
68
71
  'size',
69
72
  'spacingAfter',
70
73
  'spacingBefore',
71
- 'textColor',
74
+ 'surface',
72
75
  'theme',
73
76
  'typography',
74
77
  'until',
@@ -273,6 +276,18 @@ export const validateThemeFrontmatterStructure = (frontmatter, addIssue) =>
273
276
  fileKind: 'theme',
274
277
  });
275
278
 
279
+ export const validateRouteThemeFrontmatterStructure = (frontmatter, addIssue) =>
280
+ validateFrontmatterStructure(frontmatter, addIssue, {
281
+ knownTopLevelFrontmatterKeys: knownRouteThemeTopLevelFrontmatterKeys,
282
+ fileKind: 'route theme',
283
+ });
284
+
285
+ export const validateSitewideFrontmatterStructure = (frontmatter, addIssue) =>
286
+ validateFrontmatterStructure(frontmatter, addIssue, {
287
+ knownTopLevelFrontmatterKeys: knownSitewideTopLevelFrontmatterKeys,
288
+ fileKind: 'sitewide content',
289
+ });
290
+
276
291
  export const readThemeFile = async (sitePath) => readSiteFile(sitePath, siteThemeLabel);
277
292
 
278
293
  export const getFrontmatterSections = (frontmatter) => {
@@ -298,42 +313,11 @@ export const getFrontmatterSections = (frontmatter) => {
298
313
  return sections;
299
314
  };
300
315
 
301
- export const getFrontmatterInlineStyleNames = (frontmatter) => {
302
- const names = new Set();
303
- const lines = frontmatter.split(/\r?\n/);
304
- let inlineStylesIndent = null;
305
-
306
- for (const line of lines) {
307
- const inlineStylesMatch = line.match(/^(\s*)inlineStyles:\s*$/);
308
-
309
- if (inlineStylesMatch) {
310
- inlineStylesIndent = inlineStylesMatch[1].length;
311
- continue;
312
- }
313
-
314
- if (inlineStylesIndent === null) continue;
315
- if (!line.trim() || line.trim().startsWith('#')) continue;
316
-
317
- const indent = line.match(/^\s*/)?.[0].length ?? 0;
318
- if (indent <= inlineStylesIndent) {
319
- inlineStylesIndent = null;
320
- continue;
321
- }
322
-
323
- const nameMatch = line.match(new RegExp(`^\\s{${inlineStylesIndent + 2}}([a-z][a-z0-9-]*):\\s*$`));
324
- if (nameMatch) {
325
- names.add(nameMatch[1]);
326
- }
327
- }
328
-
329
- return names;
330
- };
316
+ export const getHeadingId = (heading) => heading.match(explicitHeadingIdRegex)?.[1];
331
317
 
332
- export const getInlineStyleReferences = (body) => Array.from(body.matchAll(inlineStyleReferenceRegex))
318
+ export const getDeprecatedInlineStyleReferences = (body) => Array.from(body.matchAll(deprecatedInlineStyleReferenceRegex))
333
319
  .map((match) => match[1]);
334
320
 
335
- export const getHeadingId = (heading) => heading.match(explicitHeadingIdRegex)?.[1];
336
-
337
321
  export const getBodySections = (body) => {
338
322
  const matches = Array.from(body.matchAll(h2Regex));
339
323
  const prelude = matches.length > 0 ? body.slice(0, matches[0].index) : body;
@@ -117,6 +117,7 @@ export const siteDir = resolvedSitePaths.siteDir;
117
117
  export const siteConfigPath = path.join(siteDir, 'config.mjs');
118
118
  export const siteThemePath = path.join(siteDir, 'theme.md');
119
119
  export const siteContentPath = path.join(siteDir, 'content.md');
120
+ export const sitewideContentPath = path.join(siteDir, 'sitewide-content.md');
120
121
  export const siteImagesDir = path.join(siteDir, 'images');
121
122
  export const siteRoutesDir = path.join(siteDir, 'routes');
122
123
  export const sitePublicDir = path.join(siteDir, 'public');
@@ -135,6 +136,7 @@ export const siteDirLabel = getPathLabel(siteDir);
135
136
  export const siteConfigLabel = getPathLabel(siteConfigPath);
136
137
  export const siteThemeLabel = getPathLabel(siteThemePath);
137
138
  export const siteContentLabel = getPathLabel(siteContentPath);
139
+ export const sitewideContentLabel = getPathLabel(sitewideContentPath);
138
140
  export const siteImagesLabel = getPathLabel(siteImagesDir);
139
141
  export const siteRoutesLabel = getPathLabel(siteRoutesDir);
140
142
  export const sitePublicLabel = getPathLabel(sitePublicDir);
@@ -0,0 +1,34 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { parseYamlMapping } from './frontmatter-yaml.mjs';
3
+ import {
4
+ sitewideContentLabel,
5
+ sitewideContentPath,
6
+ } from './site-paths.mjs';
7
+ import {
8
+ splitSiteFile,
9
+ validateFrontmatterIndentation,
10
+ validateSitewideFrontmatterStructure,
11
+ } from './site-content.mjs';
12
+
13
+ export const readSitewideConfig = async () => {
14
+ const source = await readFile(sitewideContentPath, 'utf8').catch((error) => {
15
+ if (error?.code === 'ENOENT') return null;
16
+ throw error;
17
+ });
18
+
19
+ if (!source) return {};
20
+
21
+ const { frontmatter, frontmatterBody } = splitSiteFile(source, sitewideContentLabel);
22
+ const issues = [];
23
+ validateFrontmatterIndentation(frontmatter, (issue) => issues.push(issue));
24
+ validateSitewideFrontmatterStructure(frontmatter, (issue) => issues.push(issue));
25
+
26
+ if (issues.length > 0) {
27
+ throw new Error([
28
+ `${sitewideContentLabel} has invalid frontmatter.`,
29
+ ...issues.map((issue) => `- ${issue.message}`),
30
+ ].join('\n'));
31
+ }
32
+
33
+ return parseYamlMapping(frontmatterBody);
34
+ };