@janga/norna 0.7.11 → 0.7.13

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 (239) 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 +3 -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 +221 -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 +125 -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/SiteNavigation.astro +54 -341
  205. package/src/components/SiteNotesScript.astro +43 -0
  206. package/src/components/SitePage.astro +4 -1
  207. package/src/components/SiteSection.astro +4 -47
  208. package/src/layouts/BaseLayout.astro +1 -1
  209. package/src/lib/galleryImages.ts +53 -0
  210. package/src/styles/global.css +81 -1
  211. package/starters/basic/.github/workflows/deploy.yml +1 -2
  212. package/starters/basic/README.md +10 -8
  213. package/starters/basic/site/config.md +3 -0
  214. package/starters/project/.github/workflows/deploy.yml +1 -2
  215. package/starters/project/README.md +3 -4
  216. package/starters/project/site/config.md +3 -0
  217. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro-D_GIpJIE.js.map +0 -1
  218. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/audit-DM9Hkl5B.js.map +0 -1
  219. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/embla-carousel.js.map +0 -1
  220. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/highlight-DnZ1ULOL.js.map +0 -1
  221. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/icons-RyebKi45.js.map +0 -1
  222. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/settings-DaU-gNnK.js.map +0 -1
  223. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/toolbar-63PA619H.js.map +0 -1
  224. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/ui-library--bkcNSYz.js.map +0 -1
  225. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/window-jPkpKVcy.js.map +0 -1
  226. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/xray-BXCHpEJP.js.map +0 -1
  227. package/examples/complete-sites/dog-shelter-multi-page/site/config.mjs +0 -30
  228. package/examples/complete-sites/dog-shelter-multi-page/site/routes/010-dogs/images/dogs/rex.svg +0 -19
  229. package/examples/complete-sites/dog-shelter-multi-page/site/routes/010-dogs/images/dogs/rover.svg +0 -19
  230. package/examples/complete-sites/dog-shelter-single-page/site/config.mjs +0 -30
  231. package/examples/complete-sites/dog-shelter-single-page/site/images/what-we-do/dog-house.svg +0 -25
  232. package/examples/complete-sites/dog-shelter-single-page/site/images/you-can-help/heart.svg +0 -13
  233. package/examples/feature-demos/media-and-surfaces/site/config.mjs +0 -30
  234. package/examples/feature-demos/media-and-surfaces/site/routes/010-media/theme.md +0 -3
  235. package/examples/feature-demos/theme-presets/site/config.mjs +0 -30
  236. package/fixtures/basic/site/config.mjs +0 -27
  237. package/fixtures/content-model-v2/site/config.mjs +0 -11
  238. package/starters/basic/site/config.mjs +0 -27
  239. package/starters/project/site/config.mjs +0 -30
@@ -0,0 +1,14 @@
1
+ <script>
2
+ const mobileMenu = document.querySelector<HTMLDetailsElement>('.mobile-nav-menu');
3
+ const sectionLinks = Array.from(document.querySelectorAll<HTMLAnchorElement>('.mobile-page-nav a'));
4
+
5
+ sectionLinks.forEach((link) => {
6
+ link.addEventListener('click', (event) => {
7
+ const linkUrl = new URL(link.href, window.location.href);
8
+ if (linkUrl.pathname !== window.location.pathname) return;
9
+
10
+ if (mobileMenu) mobileMenu.open = false;
11
+ if (event.detail > 0) link.blur();
12
+ });
13
+ });
14
+ </script>
@@ -3,6 +3,7 @@ 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';
6
7
 
7
8
  type FrameColors = {
8
9
  backgroundColor: string;
@@ -25,7 +26,6 @@ interface Props {
25
26
  }
26
27
 
27
28
  const { brandLabel, logo, currentPage, pageNavigation, frameColors } = Astro.props;
28
- const smoothScrollConfig = projectConfig.navigation.smoothScroll;
29
29
  const siteTopBackgroundColor = frameColors.backgroundColor === 'transparent'
30
30
  ? 'var(--color-page)'
31
31
  : frameColors.backgroundColor;
@@ -35,53 +35,61 @@ const navigationStyle = [
35
35
  ...(logo?.height ? [`--site-brand-logo-height: ${logo.height}`] : []),
36
36
  ].join('; ');
37
37
  const showSiteNavigation = pageNavigation.length > 1;
38
+ const showNavigationRow = showSiteNavigation || Boolean(logo);
38
39
  const homePage = pageNavigation.find(({ page }) => page.isHome)?.page ?? currentPage;
39
40
  const currentPageNavigation = pageNavigation.find(({ page }) => page.pathname === currentPage.pathname);
40
41
  const isCurrentPage = (page: SitePage) => page.pathname === currentPage.pathname;
41
42
  const pageHref = (page: SitePage) => withBasePath(projectConfig.site.basePath, page.pathname);
42
43
  const sectionHref = (page: SitePage, section: SectionNavigation) => `${pageHref(page)}#${section.id}`;
43
44
  const hasSectionMenu = (pageSections: SectionNavigation[]) => pageSections.length > 1;
45
+ const showSinglePageSectionNavigation = Boolean(
46
+ !showSiteNavigation && currentPageNavigation && hasSectionMenu(currentPageNavigation.sections),
47
+ );
48
+ const hasCurrentPageSectionMenu = Boolean(
49
+ showSiteNavigation && currentPageNavigation && hasSectionMenu(currentPageNavigation.sections),
50
+ );
44
51
  ---
45
52
 
46
53
  <a class="skip-link" href="#main-content">{projectConfig.locale.labels.skipToContent}</a>
47
54
 
48
55
  <header
49
56
  class:list={['site-top', { 'site-top-has-site-nav': showSiteNavigation }]}
50
- data-smooth-scroll={JSON.stringify(smoothScrollConfig)}
51
57
  style={navigationStyle}
52
58
  >
53
- {showSiteNavigation && (
59
+ {showNavigationRow && (
54
60
  <div class="site-nav-row">
55
61
  <a class="site-brand" href={pageHref(homePage)}>
56
62
  {logo ? <img class="site-brand-logo" src={withBasePath(projectConfig.site.basePath, logo.src)} alt={logo.alt} /> : brandLabel}
57
63
  </a>
58
64
 
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">
65
+ {showSiteNavigation && (
66
+ <nav class="site-nav" aria-label={projectConfig.locale.labels.siteNavigation}>
67
+ <ul>
68
+ {pageNavigation.map(({ page, sections: pageSections }) => (
69
+ <li class:list={['site-nav-item', { 'site-nav-item-has-sections': hasSectionMenu(pageSections) }]}>
70
+ <a href={pageHref(page)} aria-current={isCurrentPage(page) ? 'page' : undefined}>
71
+ {page.navigation.label}
72
+ </a>
73
+ {hasSectionMenu(pageSections) && (
74
+ <div class="site-nav-submenu">
75
+ <nav aria-label={`${page.navigation.label} ${projectConfig.locale.labels.pageNavigation}`}>
76
+ <ul>
77
+ {pageSections.map((section) => (
78
+ <li>
79
+ <a href={sectionHref(page, section)}>{section.title}</a>
80
+ </li>
81
+ ))}
82
+ </ul>
83
+ </nav>
84
+ </div>
85
+ )}
86
+ </li>
87
+ ))}
88
+ </ul>
89
+ </nav>
90
+ )}
91
+
92
+ {showSiteNavigation && <details class="mobile-nav-menu">
85
93
  <summary>
86
94
  <span>{currentPage.navigation.label}</span>
87
95
  </summary>
@@ -99,13 +107,13 @@ const hasSectionMenu = (pageSections: SectionNavigation[]) => pageSections.lengt
99
107
  </ul>
100
108
  </nav>
101
109
 
102
- {currentPageNavigation && hasSectionMenu(currentPageNavigation.sections) && (
110
+ {currentPageNavigation && hasCurrentPageSectionMenu && (
103
111
  <nav class="mobile-page-nav" aria-label={projectConfig.locale.labels.pageNavigation}>
104
112
  <h2>{projectConfig.locale.labels.pageNavigation}</h2>
105
113
  <ul>
106
- {currentPageNavigation.sections.map((section, index) => (
114
+ {currentPageNavigation.sections.map((section) => (
107
115
  <li>
108
- <a href={sectionHref(currentPage, section)} aria-current={index === 0 ? 'true' : undefined}>
116
+ <a href={sectionHref(currentPage, section)}>
109
117
  {section.title}
110
118
  </a>
111
119
  </li>
@@ -114,317 +122,22 @@ const hasSectionMenu = (pageSections: SectionNavigation[]) => pageSections.lengt
114
122
  </nav>
115
123
  )}
116
124
  </div>
117
- </details>
125
+ </details>}
118
126
  </div>
119
127
  )}
120
128
 
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
- }
129
+ {showSinglePageSectionNavigation && currentPageNavigation && (
130
+ <nav class="page-nav" aria-label={projectConfig.locale.labels.pageNavigation}>
131
+ <ul>
132
+ {currentPageNavigation.sections.map((section) => (
133
+ <li>
134
+ <a href={`#${section.id}`}>{section.title}</a>
135
+ </li>
136
+ ))}
137
+ </ul>
138
+ </nav>
139
+ )}
421
140
 
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 }));
141
+ </header>
426
142
 
427
- if (window.visualViewport) {
428
- window.visualViewport.addEventListener('resize', updateAnchorOffset);
429
- }
430
- </script>
143
+ {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
+ };