@janga/norna 0.7.11 → 0.7.12

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 (240) hide show
  1. package/README.md +120 -108
  2. package/bin/norna-cli.mjs +1 -1
  3. package/docs/README.md +107 -75
  4. package/docs/assets/norna-dog-shelter.jpg +0 -0
  5. package/docs/commands.md +7 -4
  6. package/docs/configuration.md +86 -185
  7. package/docs/design/command-organization.md +1 -1
  8. package/docs/design/norna-diagram-design.md +2 -1
  9. package/docs/design/site-examples-structure.md +4 -1
  10. package/docs/engine-development.md +15 -8
  11. package/docs/getting-started.md +126 -144
  12. package/docs/how-to/embedded-site.md +35 -0
  13. package/docs/publishing.md +47 -46
  14. package/docs/requirements.md +54 -0
  15. package/docs/routes.md +1 -1
  16. package/docs/site-structure.md +4 -4
  17. package/docs/sitewide-content.md +2 -1
  18. package/docs/theme.md +18 -0
  19. package/examples/README.md +26 -24
  20. package/examples/complete-sites/dog-shelter-multi-page/.astro/content-assets.mjs +4 -1
  21. package/examples/complete-sites/dog-shelter-multi-page/.astro/content.d.ts +1 -1
  22. package/examples/complete-sites/dog-shelter-multi-page/.astro/data-store.json +1 -1
  23. package/examples/complete-sites/dog-shelter-multi-page/.astro/dev.json +6 -8
  24. package/examples/complete-sites/dog-shelter-multi-page/.astro/dev.log +26 -53
  25. package/examples/complete-sites/dog-shelter-multi-page/README.md +7 -0
  26. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/_metadata.json +29 -35
  27. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/{astro-D_GIpJIE.js → astro-B3JHJyiv.js} +4 -4
  28. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro-B3JHJyiv.js.map +1 -0
  29. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro_n_aria-query.js +151 -151
  30. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro_n_aria-query.js.map +1 -1
  31. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro_n_axobject-query.js +131 -131
  32. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro_n_axobject-query.js.map +1 -1
  33. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro_n_html-escaper.js +1 -1
  34. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro_n_html-escaper.js.map +1 -1
  35. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +10 -10
  36. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +1 -1
  37. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/{audit-DM9Hkl5B.js → audit-C3SVr7dT.js} +10 -10
  38. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/audit-C3SVr7dT.js.map +1 -0
  39. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/{highlight-DnZ1ULOL.js → highlight-BLtvqtzO.js} +1 -1
  40. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/highlight-BLtvqtzO.js.map +1 -0
  41. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/{icons-RyebKi45.js → icons-C92i61-2.js} +1 -1
  42. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/icons-C92i61-2.js.map +1 -0
  43. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/{settings-DaU-gNnK.js → settings-v5GgKks_.js} +2 -2
  44. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/settings-v5GgKks_.js.map +1 -0
  45. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/{toolbar-63PA619H.js → toolbar-BFvJVp26.js} +2 -2
  46. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/toolbar-BFvJVp26.js.map +1 -0
  47. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/{ui-library--bkcNSYz.js → ui-library-Cd35oAvo.js} +10 -10
  48. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/ui-library-Cd35oAvo.js.map +1 -0
  49. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/{window-jPkpKVcy.js → window-vo3A6TMn.js} +1 -1
  50. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/window-vo3A6TMn.js.map +1 -0
  51. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/{xray-BXCHpEJP.js → xray-DwhzGckb.js} +3 -3
  52. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/xray-DwhzGckb.js.map +1 -0
  53. package/examples/complete-sites/dog-shelter-multi-page/site/.norna/generated-images.json +92 -12
  54. package/examples/complete-sites/dog-shelter-multi-page/site/config.md +3 -0
  55. package/examples/complete-sites/dog-shelter-multi-page/site/content.md +12 -0
  56. package/examples/complete-sites/dog-shelter-multi-page/site/images/intro/shelter-dog.jpg +0 -0
  57. package/examples/complete-sites/dog-shelter-multi-page/site/public/logo.svg +9 -0
  58. package/examples/complete-sites/dog-shelter-multi-page/site/routes/010-dogs/images/dogs/rex.jpg +0 -0
  59. package/examples/complete-sites/dog-shelter-multi-page/site/routes/010-dogs/images/dogs/rover.jpg +0 -0
  60. package/examples/complete-sites/dog-shelter-multi-page/site/routes/010-dogs/route-content.md +11 -19
  61. package/examples/complete-sites/dog-shelter-multi-page/site/routes/020-adopt/images/adopt/adoption-dog.jpg +0 -0
  62. package/examples/complete-sites/dog-shelter-multi-page/site/routes/020-adopt/route-content.md +11 -0
  63. package/examples/complete-sites/dog-shelter-multi-page/site/sitewide-content.md +12 -2
  64. package/examples/complete-sites/dog-shelter-multi-page/site/theme.md +1 -23
  65. package/examples/complete-sites/dog-shelter-single-page/.astro/collections/site.schema.json +59 -0
  66. package/examples/complete-sites/dog-shelter-single-page/.astro/collections/sitewide.schema.json +138 -0
  67. package/examples/complete-sites/dog-shelter-single-page/.astro/collections/theme.schema.json +755 -0
  68. package/examples/complete-sites/dog-shelter-single-page/.astro/content-assets.mjs +4 -0
  69. package/examples/complete-sites/dog-shelter-single-page/.astro/content-modules.mjs +1 -0
  70. package/examples/complete-sites/dog-shelter-single-page/.astro/content.d.ts +183 -0
  71. package/examples/complete-sites/dog-shelter-single-page/.astro/data-store.json +1 -0
  72. package/examples/complete-sites/dog-shelter-single-page/.astro/dev.json +13 -0
  73. package/examples/complete-sites/dog-shelter-single-page/.astro/dev.log +14 -0
  74. package/examples/complete-sites/dog-shelter-single-page/.astro/settings.json +5 -0
  75. package/examples/complete-sites/dog-shelter-single-page/.astro/types.d.ts +2 -0
  76. package/examples/complete-sites/dog-shelter-single-page/README.md +7 -0
  77. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/_metadata.json +74 -0
  78. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro-B3JHJyiv.js +434 -0
  79. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro-B3JHJyiv.js.map +1 -0
  80. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro_n_aria-query.js +6390 -0
  81. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro_n_aria-query.js.map +1 -0
  82. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro_n_axobject-query.js +2702 -0
  83. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro_n_axobject-query.js.map +1 -0
  84. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro_n_html-escaper.js +65 -0
  85. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro_n_html-escaper.js.map +1 -0
  86. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +455 -0
  87. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +1 -0
  88. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/audit-C3SVr7dT.js +1534 -0
  89. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/audit-C3SVr7dT.js.map +1 -0
  90. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/highlight-BLtvqtzO.js +48 -0
  91. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/highlight-BLtvqtzO.js.map +1 -0
  92. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/icons-C92i61-2.js +42 -0
  93. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/icons-C92i61-2.js.map +1 -0
  94. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/package.json +3 -0
  95. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +4 -0
  96. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/settings-v5GgKks_.js +190 -0
  97. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/settings-v5GgKks_.js.map +1 -0
  98. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/toolbar-BFvJVp26.js +458 -0
  99. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/toolbar-BFvJVp26.js.map +1 -0
  100. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/ui-library-Cd35oAvo.js +1016 -0
  101. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/ui-library-Cd35oAvo.js.map +1 -0
  102. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/window-vo3A6TMn.js +32 -0
  103. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/window-vo3A6TMn.js.map +1 -0
  104. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/xray-DwhzGckb.js +111 -0
  105. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/xray-DwhzGckb.js.map +1 -0
  106. package/examples/complete-sites/dog-shelter-single-page/site/.norna/generated-images.json +44 -12
  107. package/examples/complete-sites/dog-shelter-single-page/site/config.md +3 -0
  108. package/examples/complete-sites/dog-shelter-single-page/site/content.md +20 -24
  109. package/examples/complete-sites/dog-shelter-single-page/site/images/what-we-do/rescue-dog.jpg +0 -0
  110. package/examples/complete-sites/dog-shelter-single-page/site/images/you-can-help/foster-dog.jpg +0 -0
  111. package/examples/complete-sites/dog-shelter-single-page/site/public/logo.svg +9 -0
  112. package/examples/complete-sites/dog-shelter-single-page/site/sitewide-content.md +12 -2
  113. package/examples/complete-sites/dog-shelter-single-page/site/theme.md +1 -23
  114. package/examples/feature-demos/media-and-surfaces/.astro/data-store.json +1 -1
  115. package/examples/feature-demos/media-and-surfaces/.astro/dev.log +15 -2
  116. package/examples/feature-demos/media-and-surfaces/README.md +7 -1
  117. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/_metadata.json +7 -1
  118. package/examples/{complete-sites/dog-shelter-multi-page → feature-demos/media-and-surfaces}/node_modules/.vite/deps/embla-carousel.js +1 -1
  119. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/embla-carousel.js.map +1 -0
  120. package/examples/feature-demos/media-and-surfaces/site/config.md +4 -0
  121. package/examples/feature-demos/media-and-surfaces/site/content.md +5 -51
  122. package/examples/feature-demos/media-and-surfaces/site/routes/010-media/route-content.md +10 -25
  123. package/examples/feature-demos/media-and-surfaces/site/routes/020-surfaces/route-content.md +1 -1
  124. package/examples/feature-demos/sitewide-content/.astro/collections/site.schema.json +59 -0
  125. package/examples/feature-demos/sitewide-content/.astro/collections/sitewide.schema.json +138 -0
  126. package/examples/feature-demos/sitewide-content/.astro/collections/theme.schema.json +755 -0
  127. package/examples/feature-demos/sitewide-content/.astro/content-assets.mjs +1 -0
  128. package/examples/feature-demos/sitewide-content/.astro/content-modules.mjs +1 -0
  129. package/examples/feature-demos/sitewide-content/.astro/content.d.ts +183 -0
  130. package/examples/feature-demos/sitewide-content/.astro/data-store.json +1 -0
  131. package/examples/feature-demos/sitewide-content/.astro/dev.json +13 -0
  132. package/examples/feature-demos/sitewide-content/.astro/dev.log +18 -0
  133. package/examples/feature-demos/sitewide-content/.astro/settings.json +5 -0
  134. package/examples/feature-demos/sitewide-content/.astro/types.d.ts +2 -0
  135. package/examples/feature-demos/sitewide-content/README.md +18 -0
  136. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/_metadata.json +74 -0
  137. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro-B3JHJyiv.js +434 -0
  138. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro-B3JHJyiv.js.map +1 -0
  139. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro_n_aria-query.js +6390 -0
  140. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro_n_aria-query.js.map +1 -0
  141. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro_n_axobject-query.js +2702 -0
  142. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro_n_axobject-query.js.map +1 -0
  143. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro_n_html-escaper.js +65 -0
  144. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro_n_html-escaper.js.map +1 -0
  145. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +455 -0
  146. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +1 -0
  147. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/audit-C3SVr7dT.js +1534 -0
  148. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/audit-C3SVr7dT.js.map +1 -0
  149. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/highlight-BLtvqtzO.js +48 -0
  150. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/highlight-BLtvqtzO.js.map +1 -0
  151. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/icons-C92i61-2.js +42 -0
  152. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/icons-C92i61-2.js.map +1 -0
  153. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/package.json +3 -0
  154. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +4 -0
  155. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/settings-v5GgKks_.js +190 -0
  156. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/settings-v5GgKks_.js.map +1 -0
  157. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/toolbar-BFvJVp26.js +458 -0
  158. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/toolbar-BFvJVp26.js.map +1 -0
  159. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/ui-library-Cd35oAvo.js +1016 -0
  160. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/ui-library-Cd35oAvo.js.map +1 -0
  161. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/window-vo3A6TMn.js +32 -0
  162. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/window-vo3A6TMn.js.map +1 -0
  163. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/xray-DwhzGckb.js +111 -0
  164. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/xray-DwhzGckb.js.map +1 -0
  165. package/examples/feature-demos/sitewide-content/site/.norna/generated-images.json +1 -0
  166. package/examples/feature-demos/sitewide-content/site/config.md +3 -0
  167. package/examples/feature-demos/sitewide-content/site/content.md +16 -0
  168. package/examples/feature-demos/sitewide-content/site/public/logo.svg +6 -0
  169. package/examples/feature-demos/sitewide-content/site/public/robots.txt +2 -0
  170. package/examples/feature-demos/sitewide-content/site/routes/010-identity/route-content.md +20 -0
  171. package/examples/feature-demos/sitewide-content/site/routes/020-notices/route-content.md +22 -0
  172. package/examples/feature-demos/sitewide-content/site/routes/030-footer/route-content.md +17 -0
  173. package/examples/feature-demos/sitewide-content/site/sitewide-content.md +33 -0
  174. package/examples/feature-demos/sitewide-content/site/theme.md +3 -0
  175. package/examples/feature-demos/theme-presets/.astro/dev.log +37 -0
  176. package/examples/feature-demos/theme-presets/README.md +5 -1
  177. package/examples/feature-demos/theme-presets/site/config.md +3 -0
  178. package/fixtures/basic/site/config.md +3 -0
  179. package/fixtures/content-model-v2/site/config.md +3 -0
  180. package/package.json +12 -7
  181. package/scripts/build-pages.mjs +4 -6
  182. package/scripts/check-config.mjs +1 -10
  183. package/scripts/deploy-site.mjs +12 -8
  184. package/scripts/lib/example-sites.mjs +3 -0
  185. package/scripts/lib/github-repository.mjs +44 -0
  186. package/scripts/lib/norna-markdown-blocks.mjs +97 -10
  187. package/scripts/lib/project-config.mjs +112 -123
  188. package/scripts/lib/site-content.mjs +7 -0
  189. package/scripts/lib/site-paths.mjs +6 -2
  190. package/scripts/test-client-javascript.mjs +236 -0
  191. package/scripts/test-content-check.mjs +103 -0
  192. package/scripts/test-content-model-v2.mjs +9 -13
  193. package/scripts/test-documentation.mjs +116 -0
  194. package/scripts/test-engine-commands.mjs +12 -3
  195. package/scripts/test-examples.mjs +9 -0
  196. package/scripts/test-package-check.mjs +17 -9
  197. package/scripts/test-project-config.mjs +130 -0
  198. package/scripts/test-temporary-visibility.mjs +3 -10
  199. package/scripts/test-theme-presets.mjs +3 -8
  200. package/scripts/watch-pages-deploy.mjs +15 -11
  201. package/src/components/GalleryGrid.astro +30 -230
  202. package/src/components/ImageCarousel.astro +184 -0
  203. package/src/components/MobileSectionNavigationScript.astro +14 -0
  204. package/src/components/NornaSmoothScrollScript.astro +250 -0
  205. package/src/components/SiteNavigation.astro +58 -341
  206. package/src/components/SiteNotesScript.astro +43 -0
  207. package/src/components/SitePage.astro +4 -1
  208. package/src/components/SiteSection.astro +4 -47
  209. package/src/layouts/BaseLayout.astro +1 -1
  210. package/src/lib/galleryImages.ts +53 -0
  211. package/src/styles/global.css +81 -1
  212. package/starters/basic/.github/workflows/deploy.yml +1 -2
  213. package/starters/basic/README.md +10 -8
  214. package/starters/basic/site/config.md +3 -0
  215. package/starters/project/.github/workflows/deploy.yml +1 -2
  216. package/starters/project/README.md +3 -4
  217. package/starters/project/site/config.md +3 -0
  218. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro-D_GIpJIE.js.map +0 -1
  219. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/audit-DM9Hkl5B.js.map +0 -1
  220. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/embla-carousel.js.map +0 -1
  221. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/highlight-DnZ1ULOL.js.map +0 -1
  222. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/icons-RyebKi45.js.map +0 -1
  223. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/settings-DaU-gNnK.js.map +0 -1
  224. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/toolbar-63PA619H.js.map +0 -1
  225. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/ui-library--bkcNSYz.js.map +0 -1
  226. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/window-jPkpKVcy.js.map +0 -1
  227. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/xray-BXCHpEJP.js.map +0 -1
  228. package/examples/complete-sites/dog-shelter-multi-page/site/config.mjs +0 -30
  229. package/examples/complete-sites/dog-shelter-multi-page/site/routes/010-dogs/images/dogs/rex.svg +0 -19
  230. package/examples/complete-sites/dog-shelter-multi-page/site/routes/010-dogs/images/dogs/rover.svg +0 -19
  231. package/examples/complete-sites/dog-shelter-single-page/site/config.mjs +0 -30
  232. package/examples/complete-sites/dog-shelter-single-page/site/images/what-we-do/dog-house.svg +0 -25
  233. package/examples/complete-sites/dog-shelter-single-page/site/images/you-can-help/heart.svg +0 -13
  234. package/examples/feature-demos/media-and-surfaces/site/config.mjs +0 -30
  235. package/examples/feature-demos/media-and-surfaces/site/routes/010-media/theme.md +0 -3
  236. package/examples/feature-demos/theme-presets/site/config.mjs +0 -30
  237. package/fixtures/basic/site/config.mjs +0 -27
  238. package/fixtures/content-model-v2/site/config.mjs +0 -11
  239. package/starters/basic/site/config.mjs +0 -27
  240. package/starters/project/site/config.mjs +0 -30
@@ -3,6 +3,8 @@ import projectConfig from '../../scripts/lib/project-config.mjs';
3
3
  import { withBasePath } from '../lib/basePath';
4
4
  import type { SectionNavigation } from '../lib/sectionContent';
5
5
  import type { SitePage } from '../lib/sitePages';
6
+ import MobileSectionNavigationScript from './MobileSectionNavigationScript.astro';
7
+ import NornaSmoothScrollScript from './NornaSmoothScrollScript.astro';
6
8
 
7
9
  type FrameColors = {
8
10
  backgroundColor: string;
@@ -25,7 +27,6 @@ interface Props {
25
27
  }
26
28
 
27
29
  const { brandLabel, logo, currentPage, pageNavigation, frameColors } = Astro.props;
28
- const smoothScrollConfig = projectConfig.navigation.smoothScroll;
29
30
  const siteTopBackgroundColor = frameColors.backgroundColor === 'transparent'
30
31
  ? 'var(--color-page)'
31
32
  : frameColors.backgroundColor;
@@ -35,53 +36,62 @@ const navigationStyle = [
35
36
  ...(logo?.height ? [`--site-brand-logo-height: ${logo.height}`] : []),
36
37
  ].join('; ');
37
38
  const showSiteNavigation = pageNavigation.length > 1;
39
+ const showNavigationRow = showSiteNavigation || Boolean(logo);
38
40
  const homePage = pageNavigation.find(({ page }) => page.isHome)?.page ?? currentPage;
39
41
  const currentPageNavigation = pageNavigation.find(({ page }) => page.pathname === currentPage.pathname);
40
42
  const isCurrentPage = (page: SitePage) => page.pathname === currentPage.pathname;
41
43
  const pageHref = (page: SitePage) => withBasePath(projectConfig.site.basePath, page.pathname);
42
44
  const sectionHref = (page: SitePage, section: SectionNavigation) => `${pageHref(page)}#${section.id}`;
43
45
  const hasSectionMenu = (pageSections: SectionNavigation[]) => pageSections.length > 1;
46
+ const showSinglePageSectionNavigation = Boolean(
47
+ !showSiteNavigation && currentPageNavigation && hasSectionMenu(currentPageNavigation.sections),
48
+ );
49
+ const hasCurrentPageSectionMenu = Boolean(
50
+ showSiteNavigation && currentPageNavigation && hasSectionMenu(currentPageNavigation.sections),
51
+ );
52
+ const useNornaSmoothScroll = projectConfig.navigation.scrollBehavior === 'norna-smooth';
44
53
  ---
45
54
 
46
55
  <a class="skip-link" href="#main-content">{projectConfig.locale.labels.skipToContent}</a>
47
56
 
48
57
  <header
49
58
  class:list={['site-top', { 'site-top-has-site-nav': showSiteNavigation }]}
50
- data-smooth-scroll={JSON.stringify(smoothScrollConfig)}
51
59
  style={navigationStyle}
52
60
  >
53
- {showSiteNavigation && (
61
+ {showNavigationRow && (
54
62
  <div class="site-nav-row">
55
63
  <a class="site-brand" href={pageHref(homePage)}>
56
64
  {logo ? <img class="site-brand-logo" src={withBasePath(projectConfig.site.basePath, logo.src)} alt={logo.alt} /> : brandLabel}
57
65
  </a>
58
66
 
59
- <nav class="site-nav" aria-label={projectConfig.locale.labels.siteNavigation}>
60
- <ul>
61
- {pageNavigation.map(({ page, sections: pageSections }) => (
62
- <li class:list={['site-nav-item', { 'site-nav-item-has-sections': hasSectionMenu(pageSections) }]}>
63
- <a href={pageHref(page)} aria-current={isCurrentPage(page) ? 'page' : undefined}>
64
- {page.navigation.label}
65
- </a>
66
- {hasSectionMenu(pageSections) && (
67
- <div class="site-nav-submenu">
68
- <nav aria-label={`${page.navigation.label} ${projectConfig.locale.labels.pageNavigation}`}>
69
- <ul>
70
- {pageSections.map((section) => (
71
- <li>
72
- <a href={sectionHref(page, section)}>{section.title}</a>
73
- </li>
74
- ))}
75
- </ul>
76
- </nav>
77
- </div>
78
- )}
79
- </li>
80
- ))}
81
- </ul>
82
- </nav>
83
-
84
- <details class="mobile-nav-menu">
67
+ {showSiteNavigation && (
68
+ <nav class="site-nav" aria-label={projectConfig.locale.labels.siteNavigation}>
69
+ <ul>
70
+ {pageNavigation.map(({ page, sections: pageSections }) => (
71
+ <li class:list={['site-nav-item', { 'site-nav-item-has-sections': hasSectionMenu(pageSections) }]}>
72
+ <a href={pageHref(page)} aria-current={isCurrentPage(page) ? 'page' : undefined}>
73
+ {page.navigation.label}
74
+ </a>
75
+ {hasSectionMenu(pageSections) && (
76
+ <div class="site-nav-submenu">
77
+ <nav aria-label={`${page.navigation.label} ${projectConfig.locale.labels.pageNavigation}`}>
78
+ <ul>
79
+ {pageSections.map((section) => (
80
+ <li>
81
+ <a href={sectionHref(page, section)}>{section.title}</a>
82
+ </li>
83
+ ))}
84
+ </ul>
85
+ </nav>
86
+ </div>
87
+ )}
88
+ </li>
89
+ ))}
90
+ </ul>
91
+ </nav>
92
+ )}
93
+
94
+ {showSiteNavigation && <details class="mobile-nav-menu">
85
95
  <summary>
86
96
  <span>{currentPage.navigation.label}</span>
87
97
  </summary>
@@ -99,13 +109,13 @@ const hasSectionMenu = (pageSections: SectionNavigation[]) => pageSections.lengt
99
109
  </ul>
100
110
  </nav>
101
111
 
102
- {currentPageNavigation && hasSectionMenu(currentPageNavigation.sections) && (
112
+ {currentPageNavigation && hasCurrentPageSectionMenu && (
103
113
  <nav class="mobile-page-nav" aria-label={projectConfig.locale.labels.pageNavigation}>
104
114
  <h2>{projectConfig.locale.labels.pageNavigation}</h2>
105
115
  <ul>
106
- {currentPageNavigation.sections.map((section, index) => (
116
+ {currentPageNavigation.sections.map((section) => (
107
117
  <li>
108
- <a href={sectionHref(currentPage, section)} aria-current={index === 0 ? 'true' : undefined}>
118
+ <a href={sectionHref(currentPage, section)}>
109
119
  {section.title}
110
120
  </a>
111
121
  </li>
@@ -114,317 +124,24 @@ const hasSectionMenu = (pageSections: SectionNavigation[]) => pageSections.lengt
114
124
  </nav>
115
125
  )}
116
126
  </div>
117
- </details>
127
+ </details>}
118
128
  </div>
119
129
  )}
120
130
 
121
- </header>
122
-
123
- <script>
124
- const siteTop = document.querySelector('.site-top');
125
- const sections = Array.from(document.querySelectorAll('.site-section'));
126
- const links = Array.from(document.querySelectorAll('.site-nav-submenu a, .mobile-page-nav a'));
127
- const mobileMenu = document.querySelector('.mobile-nav-menu');
128
- const smoothScrollConfig = JSON.parse(siteTop?.getAttribute('data-smooth-scroll') ?? '{}');
129
- const anchorPadding = 0;
130
- const anchorCorrectionDelays = [120, 320, 700, 1_000, 2_000];
131
- const reduceMotionQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
132
- const minimumScrollDuration = smoothScrollConfig.minimumDurationMs;
133
- const maximumScrollDuration = smoothScrollConfig.maximumDurationMs;
134
- const scrollDurationPerPixel = smoothScrollConfig.durationPerPixelMs;
135
- const scrollInterruptEvents = ['wheel', 'touchstart'];
136
- const scrollInterruptKeys = new Set(['ArrowDown', 'ArrowUp', 'End', 'Home', 'PageDown', 'PageUp', ' ']);
137
- let activeScrollAnimation = null;
138
- let activeSectionId = null;
139
- let anchorCorrectionToken = 0;
140
-
141
- if ('scrollRestoration' in history) {
142
- history.scrollRestoration = 'manual';
143
- }
144
-
145
- const setAnchorOffset = () => {
146
- if (!(siteTop instanceof HTMLElement)) return;
147
-
148
- const offset = Math.ceil(siteTop.getBoundingClientRect().height + anchorPadding);
149
- document.documentElement.style.setProperty('--site-top-anchor-offset', `${offset}px`);
150
- return offset;
151
- };
152
-
153
- const getSectionFromHash = (hash) => {
154
- const id = hash.replace(/^#/, '');
155
- return sections.find((section) => section.id === id);
156
- };
157
-
158
- const getSectionHeading = (section) => section?.querySelector('h1, h2');
159
-
160
- const cancelActiveScroll = () => {
161
- if (activeScrollAnimation) {
162
- activeScrollAnimation.cancel();
163
- activeScrollAnimation = null;
164
- }
165
- };
166
-
167
- const setActiveSection = (section) => {
168
- if (!section) return;
169
- if (activeSectionId === section.id) return;
170
-
171
- activeSectionId = section.id;
172
-
173
- links.forEach((link) => {
174
- const linkUrl = new URL(link.href, window.location.href);
175
- const isCurrentPageLink = linkUrl.pathname === window.location.pathname;
176
- const isActive = isCurrentPageLink && linkUrl.hash === `#${section.id}`;
177
- if (isActive) {
178
- link.setAttribute('aria-current', 'true');
179
- } else {
180
- link.removeAttribute('aria-current');
181
- }
182
- });
183
- };
184
-
185
- const getHashSection = () => getSectionFromHash(window.location.hash);
186
-
187
- const setHash = (targetHash) => {
188
- if (window.location.hash !== targetHash) {
189
- history.pushState(null, '', targetHash);
190
- }
191
- };
192
-
193
- const closeMobileMenu = () => {
194
- if (mobileMenu instanceof HTMLDetailsElement) {
195
- mobileMenu.open = false;
196
- }
197
- };
198
-
199
- const getSectionScrollTop = (section) => {
200
- if (!(section instanceof HTMLElement)) return;
201
- const heading = getSectionHeading(section);
202
- if (!(heading instanceof HTMLElement)) return;
203
-
204
- const offset = setAnchorOffset();
205
- if (!offset) return;
206
-
207
- return Math.max(0, heading.getBoundingClientRect().top + window.scrollY - offset);
208
- };
209
-
210
- const jumpTo = (top) => {
211
- cancelActiveScroll();
212
-
213
- const root = document.documentElement;
214
- const previousScrollBehavior = root.style.scrollBehavior;
215
- root.style.scrollBehavior = 'auto';
216
- window.scrollTo(0, top);
217
- root.style.scrollBehavior = previousScrollBehavior;
218
- };
219
-
220
- const getScrollDuration = (distance) => {
221
- const distancePx = Math.abs(distance);
222
-
223
- if (!smoothScrollConfig.enabled || reduceMotionQuery.matches || distancePx <= anchorPadding) return 0;
224
-
225
- return Math.min(
226
- maximumScrollDuration,
227
- Math.max(minimumScrollDuration, distancePx * scrollDurationPerPixel),
228
- );
229
- };
230
-
231
- const easeInOutCubic = (progress) => (
232
- progress < 0.5
233
- ? 4 * progress * progress * progress
234
- : 1 - Math.pow(-2 * progress + 2, 3) / 2
235
- );
236
-
237
- const animateScrollTo = (
238
- target,
239
- { duration, onComplete } = {},
240
- ) => {
241
- cancelActiveScroll();
242
-
243
- const getTop = typeof target === 'function' ? target : () => target;
244
- let latestTop = getTop();
245
- if (latestTop === undefined) return 0;
246
-
247
- const startY = window.scrollY;
248
- const distance = latestTop - startY;
249
- const scrollDuration = duration ?? getScrollDuration(distance);
250
-
251
- if (scrollDuration <= 0) {
252
- const top = getTop();
253
- if (top === undefined) return 0;
254
-
255
- jumpTo(top);
256
- onComplete?.();
257
- return 0;
258
- }
259
-
260
- const root = document.documentElement;
261
- const previousScrollBehavior = root.style.scrollBehavior;
262
- const startedAt = performance.now();
263
- let frameId = 0;
264
-
265
- const state = {
266
- cancel: () => {},
267
- };
268
-
269
- const cleanup = () => {
270
- scrollInterruptEvents.forEach((eventName) => {
271
- window.removeEventListener(eventName, interruptScroll);
272
- });
273
- window.removeEventListener('keydown', interruptScrollKey);
274
- root.style.scrollBehavior = previousScrollBehavior;
275
-
276
- if (activeScrollAnimation === state) {
277
- activeScrollAnimation = null;
278
- }
279
- };
280
-
281
- const cancel = () => {
282
- if (frameId) {
283
- cancelAnimationFrame(frameId);
284
- }
285
-
286
- cleanup();
287
- };
288
-
289
- const interruptScroll = () => cancel();
290
- const interruptScrollKey = (event) => {
291
- if (scrollInterruptKeys.has(event.key)) {
292
- interruptScroll();
293
- }
294
- };
295
-
296
- const step = (now) => {
297
- const elapsed = now - startedAt;
298
- const progress = Math.min(1, elapsed / scrollDuration);
299
- const top = getTop();
300
- if (top !== undefined) {
301
- latestTop = top;
302
- }
303
-
304
- const nextY = startY + (latestTop - startY) * easeInOutCubic(progress);
305
-
306
- window.scrollTo(0, nextY);
307
-
308
- if (progress >= 1) {
309
- window.scrollTo(0, latestTop);
310
- cleanup();
311
- onComplete?.();
312
- return;
313
- }
314
-
315
- frameId = requestAnimationFrame(step);
316
- };
317
-
318
- state.cancel = cancel;
319
- activeScrollAnimation = state;
320
- root.style.scrollBehavior = 'auto';
321
- scrollInterruptEvents.forEach((eventName) => {
322
- window.addEventListener(eventName, interruptScroll, { passive: true });
323
- });
324
- window.addEventListener('keydown', interruptScrollKey);
325
- frameId = requestAnimationFrame(step);
326
-
327
- return scrollDuration;
328
- };
329
-
330
- const scrollToSection = (section, { behavior = 'smooth', duration, onComplete } = {}) => {
331
- const top = getSectionScrollTop(section);
332
- if (top === undefined) return 0;
333
-
334
- const scrollDuration = behavior === 'smooth'
335
- ? animateScrollTo(() => getSectionScrollTop(section), { duration, onComplete })
336
- : (jumpTo(top), onComplete?.(), 0);
337
-
338
- setActiveSection(section);
339
-
340
- return scrollDuration;
341
- };
342
-
343
- const updateAnchorOffset = () => {
344
- setAnchorOffset();
345
- };
346
-
347
- const correctCurrentHashAnchor = () => {
348
- if (activeScrollAnimation) return;
349
-
350
- const section = getHashSection();
351
- if (section instanceof HTMLElement) {
352
- scrollToSection(section, { behavior: 'auto' });
353
- }
354
- };
355
-
356
- const scheduleHashAnchorCorrection = () => {
357
- if (!window.location.hash) return;
358
-
359
- anchorCorrectionToken += 1;
360
- const token = anchorCorrectionToken;
361
-
362
- anchorCorrectionDelays.forEach((delay) => {
363
- window.setTimeout(() => {
364
- if (token === anchorCorrectionToken) {
365
- requestAnimationFrame(correctCurrentHashAnchor);
366
- }
367
- }, delay);
368
- });
369
- };
370
-
371
- const syncToLocation = ({ behavior = 'auto', scrollHashlessTop = false } = {}) => {
372
- const section = getHashSection();
373
-
374
- if (section instanceof HTMLElement) {
375
- scrollToSection(section, { behavior });
376
- scheduleHashAnchorCorrection();
377
- return;
378
- }
379
-
380
- if (scrollHashlessTop) {
381
- jumpTo(0);
382
- }
383
-
384
- setActiveSection(sections[0]);
385
- };
386
-
387
- links.forEach((link) => {
388
- link.addEventListener('click', (event) => {
389
- const linkUrl = new URL(link.href, window.location.href);
390
- if (linkUrl.pathname !== window.location.pathname) return;
391
-
392
- const section = getSectionFromHash(linkUrl.hash);
393
- if (!(section instanceof HTMLElement)) return;
394
-
395
- const top = getSectionScrollTop(section);
396
- if (top === undefined) return;
397
-
398
- event.preventDefault();
399
-
400
- const targetHash = linkUrl.hash;
401
- const duration = getScrollDuration(top - window.scrollY);
402
-
403
- setHash(targetHash);
404
- setActiveSection(section);
405
- closeMobileMenu();
406
-
407
- scrollToSection(section, {
408
- behavior: 'smooth',
409
- duration,
410
- onComplete: scheduleHashAnchorCorrection,
411
- });
412
- });
413
- });
414
-
415
- updateAnchorOffset();
416
- requestAnimationFrame(() => syncToLocation({ behavior: 'auto' }));
417
-
418
- if (siteTop instanceof HTMLElement && 'ResizeObserver' in window) {
419
- new ResizeObserver(updateAnchorOffset).observe(siteTop);
420
- }
131
+ {showSinglePageSectionNavigation && currentPageNavigation && (
132
+ <nav class="page-nav" aria-label={projectConfig.locale.labels.pageNavigation}>
133
+ <ul>
134
+ {currentPageNavigation.sections.map((section) => (
135
+ <li>
136
+ <a href={`#${section.id}`}>{section.title}</a>
137
+ </li>
138
+ ))}
139
+ </ul>
140
+ </nav>
141
+ )}
421
142
 
422
- window.addEventListener('resize', updateAnchorOffset);
423
- window.addEventListener('load', scheduleHashAnchorCorrection);
424
- window.addEventListener('hashchange', () => syncToLocation({ behavior: 'auto', scrollHashlessTop: true }));
425
- window.addEventListener('popstate', () => syncToLocation({ behavior: 'auto', scrollHashlessTop: true }));
143
+ </header>
426
144
 
427
- if (window.visualViewport) {
428
- window.visualViewport.addEventListener('resize', updateAnchorOffset);
429
- }
430
- </script>
145
+ {useNornaSmoothScroll
146
+ ? <NornaSmoothScrollScript />
147
+ : hasCurrentPageSectionMenu && <MobileSectionNavigationScript />}
@@ -0,0 +1,43 @@
1
+ <script>
2
+ const noteParts = Array.from(document.querySelectorAll<HTMLElement>('[data-note-id]'));
3
+ const noteReferences = noteParts.filter((part) => part.matches('.section-note-ref a'));
4
+ const marginNotes = noteParts.filter((part) => part.matches('.section-note'));
5
+
6
+ const setNoteState = (noteId: string, active: boolean) => {
7
+ noteParts
8
+ .filter((part) => part.dataset.noteId === noteId)
9
+ .forEach((part) => part.classList.toggle('is-referenced', active));
10
+ };
11
+
12
+ noteParts.forEach((part) => {
13
+ const noteId = part.dataset.noteId;
14
+ if (!noteId) return;
15
+
16
+ part.addEventListener('pointerenter', () => setNoteState(noteId, true));
17
+ part.addEventListener('pointerleave', () => setNoteState(noteId, false));
18
+ part.addEventListener('focusin', () => setNoteState(noteId, true));
19
+ part.addEventListener('focusout', () => setNoteState(noteId, false));
20
+ });
21
+
22
+ const alignMarginNotes = () => {
23
+ const wideLayout = window.matchMedia('(min-width: 1101px)').matches;
24
+ if (!wideLayout) {
25
+ marginNotes.forEach((note) => note.style.removeProperty('--section-note-offset'));
26
+ return;
27
+ }
28
+
29
+ marginNotes.forEach((note) => {
30
+ const noteId = note.dataset.noteId;
31
+ const reference = noteReferences.find((part) => part.dataset.noteId === noteId);
32
+ if (!noteId || !reference) return;
33
+
34
+ note.style.removeProperty('--section-note-offset');
35
+ const offset = reference.getBoundingClientRect().top - note.getBoundingClientRect().top;
36
+ note.style.setProperty('--section-note-offset', `${Math.max(0, offset)}px`);
37
+ });
38
+ };
39
+
40
+ alignMarginNotes();
41
+ window.addEventListener('resize', alignMarginNotes);
42
+ document.fonts?.ready.then(alignMarginNotes);
43
+ </script>
@@ -4,6 +4,7 @@ import path from 'node:path';
4
4
  import BaseLayout from '../layouts/BaseLayout.astro';
5
5
  import SiteNavigation from './SiteNavigation.astro';
6
6
  import SiteBanners from './SiteBanners.astro';
7
+ import SiteNotesScript from './SiteNotesScript.astro';
7
8
  import SiteSection from './SiteSection.astro';
8
9
  import { getSectionNavigation, getSectionsContent } from '../lib/sectionContent';
9
10
  import { getRouteTheme } from '../lib/routeThemes';
@@ -65,6 +66,7 @@ const resolvedSections = await getSectionsContent(siteHtml, rawMarkdown, section
65
66
  const today = getToday();
66
67
  const siteBanners = (sitewideData.banners ?? []).filter((banner) => isVisibleToday(banner.visible, today));
67
68
  const visibleSections = resolvedSections.filter((section) => isVisibleToday(section.visible, today));
69
+ const hasMarginNotes = visibleSections.some((section) => section.contentBlocks.some((block) => block.type === 'note'));
68
70
 
69
71
  const getPageContent = async (page: SitePage) => {
70
72
  if (page.pathname === currentPage.pathname) {
@@ -102,7 +104,7 @@ if (visibleSections.length === 0) {
102
104
  pageNavigation={pageNavigation}
103
105
  frameColors={frameColors}
104
106
  />
105
- <SiteBanners banners={siteBanners} />
107
+ {siteBanners.length > 0 && <SiteBanners banners={siteBanners} />}
106
108
 
107
109
  <main id="main-content" class="site-content">
108
110
  {visibleSections.map((section, index) => (
@@ -116,4 +118,5 @@ if (visibleSections.length === 0) {
116
118
  />
117
119
  ))}
118
120
  </main>
121
+ {hasMarginNotes && <SiteNotesScript />}
119
122
  </BaseLayout>
@@ -3,6 +3,7 @@ import projectConfig from '../../scripts/lib/project-config.mjs';
3
3
  import { resolveSectionSurface } from '../../scripts/lib/presentation.mjs';
4
4
  import CardList from './CardList.astro';
5
5
  import GalleryGrid from './GalleryGrid.astro';
6
+ import ImageCarousel from './ImageCarousel.astro';
6
7
  import type { ResolvedSection } from '../lib/sectionContent';
7
8
 
8
9
  type ResponsiveValue<T> = { desktop: T; mobile: T };
@@ -379,9 +380,9 @@ const sectionStyle = [
379
380
  const block = group.block;
380
381
  if (block.type === 'image-carousel') {
381
382
  return (
382
- <GalleryGrid
383
+ <ImageCarousel
383
384
  title={section.title}
384
- items={[{ carousel: block.images }]}
385
+ images={block.images}
385
386
  maxAvailableWidthPercent={visualTheme.gallery.maxAvailableWidthPercent}
386
387
  priorityFirstImage={priorityGalleryImage && groupIndex === 0}
387
388
  />
@@ -395,7 +396,7 @@ const sectionStyle = [
395
396
  return (
396
397
  <GalleryGrid
397
398
  title={section.title}
398
- items={block.images}
399
+ images={block.images}
399
400
  maxAvailableWidthPercent={visualTheme.gallery.maxAvailableWidthPercent}
400
401
  priorityFirstImage={priorityGalleryImage && groupIndex === 0}
401
402
  />
@@ -404,47 +405,3 @@ const sectionStyle = [
404
405
  </div>
405
406
  </div>
406
407
  </section>
407
-
408
- <script>
409
- const noteParts = Array.from(document.querySelectorAll<HTMLElement>('[data-note-id]'));
410
- const noteReferences = noteParts.filter((part) => part.matches('.section-note-ref a'));
411
- const marginNotes = noteParts.filter((part) => part.matches('.section-note'));
412
-
413
- const setNoteState = (noteId: string, active: boolean) => {
414
- noteParts
415
- .filter((part) => part.dataset.noteId === noteId)
416
- .forEach((part) => part.classList.toggle('is-referenced', active));
417
- };
418
-
419
- noteParts.forEach((part) => {
420
- const noteId = part.dataset.noteId;
421
- if (!noteId) return;
422
-
423
- part.addEventListener('pointerenter', () => setNoteState(noteId, true));
424
- part.addEventListener('pointerleave', () => setNoteState(noteId, false));
425
- part.addEventListener('focusin', () => setNoteState(noteId, true));
426
- part.addEventListener('focusout', () => setNoteState(noteId, false));
427
- });
428
-
429
- const alignMarginNotes = () => {
430
- const wideLayout = window.matchMedia('(min-width: 1101px)').matches;
431
- if (!wideLayout) {
432
- marginNotes.forEach((note) => note.style.removeProperty('--section-note-offset'));
433
- return;
434
- }
435
-
436
- marginNotes.forEach((note) => {
437
- const noteId = note.dataset.noteId;
438
- const reference = noteReferences.find((part) => part.dataset.noteId === noteId);
439
- if (!noteId || !reference) return;
440
-
441
- note.style.removeProperty('--section-note-offset');
442
- const offset = reference.getBoundingClientRect().top - note.getBoundingClientRect().top;
443
- note.style.setProperty('--section-note-offset', `${Math.max(0, offset)}px`);
444
- });
445
- };
446
-
447
- alignMarginNotes();
448
- window.addEventListener('resize', alignMarginNotes);
449
- document.fonts?.ready.then(alignMarginNotes);
450
- </script>
@@ -73,7 +73,7 @@ const {
73
73
  const documentTitle = title;
74
74
  const canonicalUrl = new URL(withBasePath(projectConfig.site.basePath, pathname), projectConfig.site.url).href;
75
75
  const iconLinks = getIconLinks();
76
- const scrollBehavior = projectConfig.navigation.smoothScroll.enabled ? 'smooth' : 'auto';
76
+ const scrollBehavior = projectConfig.navigation.scrollBehavior === 'browser-smooth' ? 'smooth' : 'auto';
77
77
  const formatPercent = (value: number) => `${value}%`;
78
78
  const formatViewportHeightPercent = (value: number) => `${value}svh`;
79
79
  const layoutSpacing = visualTheme.layout.spacing;
@@ -0,0 +1,53 @@
1
+ import { getGeneratedImage } from './generatedImages';
2
+
3
+ export type GalleryImage = {
4
+ image: string;
5
+ src?: string;
6
+ alt?: string;
7
+ caption?: string;
8
+ };
9
+
10
+ type GeneratedImageWithDimensions = NonNullable<ReturnType<typeof getGeneratedImage>> & {
11
+ width: number;
12
+ height: number;
13
+ };
14
+
15
+ const hasDimensions = (image: ReturnType<typeof getGeneratedImage>): image is GeneratedImageWithDimensions => (
16
+ Boolean(image) && Number.isFinite(image.width) && Number.isFinite(image.height)
17
+ );
18
+
19
+ export const getCaptionId = (image: GalleryImage, itemIndex: number, imageIndex = 0) => {
20
+ const source = (image.src ?? image.image)
21
+ .toLowerCase()
22
+ .replace(/[^a-z0-9]+/g, '-')
23
+ .replace(/^-|-$/g, '');
24
+
25
+ return `gallery-caption-${source}-${itemIndex}-${imageIndex}`;
26
+ };
27
+
28
+ export const getGalleryImageSizes = (maxAvailableWidthPercent: { desktop: number; mobile: number }) => [
29
+ `(max-width: 700px) ${maxAvailableWidthPercent.mobile}vw`,
30
+ `${maxAvailableWidthPercent.desktop}vw`,
31
+ ].join(', ');
32
+
33
+ export const getImageClass = (image: GalleryImage) => {
34
+ const generatedImage = getGeneratedImage(image.src ?? image.image);
35
+ return hasDimensions(generatedImage) && generatedImage.width > generatedImage.height
36
+ ? 'gallery-image-landscape'
37
+ : undefined;
38
+ };
39
+
40
+ export const getImageLoadingProps = (isPriority: boolean) => ({
41
+ decoding: 'async' as const,
42
+ fetchpriority: isPriority ? 'high' as const : 'low' as const,
43
+ loading: isPriority ? 'eager' as const : 'lazy' as const,
44
+ });
45
+
46
+ export const getCarouselAspectRatio = (images: GalleryImage[]) => {
47
+ const ratios = images
48
+ .map((image) => getGeneratedImage(image.src ?? image.image))
49
+ .filter(hasDimensions)
50
+ .map((image) => image.width / image.height);
51
+
52
+ return ratios.length > 0 ? Math.min(...ratios) : null;
53
+ };