@morphemecms/cms 0.5.0 → 0.7.0

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 (396) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/astro.mjs +3 -0
  3. package/bin/morpheme.mjs +3 -1
  4. package/defaults/blockTexts.ts +2 -2
  5. package/defaults/collections.ts +10 -0
  6. package/defaults/editor.ts +57 -53
  7. package/defaults/privacy.ts +4 -1
  8. package/defaults/rules.ts +36 -38
  9. package/defaults/standardPages.ts +0 -3
  10. package/defaults/texts.ts +11 -2
  11. package/demo/src/content/pages/about.md +2 -0
  12. package/icons/lucide/browser-fill.svg +1 -0
  13. package/icons/lucide/buildings-fill.svg +1 -0
  14. package/icons/lucide/certificate-fill.svg +1 -0
  15. package/icons/lucide/chart-line-up-fill.svg +1 -0
  16. package/icons/lucide/chats-fill.svg +1 -0
  17. package/icons/lucide/envelope-simple-fill.svg +1 -0
  18. package/icons/lucide/file-text-fill.svg +1 -0
  19. package/icons/lucide/folder-simple-fill.svg +1 -0
  20. package/icons/lucide/gauge.svg +1 -1
  21. package/icons/lucide/gear-six.svg +1 -0
  22. package/icons/lucide/globe-simple.svg +1 -0
  23. package/icons/lucide/heart-fill.svg +1 -0
  24. package/icons/lucide/layout.svg +1 -0
  25. package/icons/lucide/list-checks-fill.svg +1 -0
  26. package/icons/lucide/magnifying-glass-fill.svg +1 -0
  27. package/icons/lucide/megaphone-fill.svg +1 -0
  28. package/icons/lucide/newspaper-fill.svg +1 -0
  29. package/icons/lucide/paint-brush-broad-fill.svg +1 -0
  30. package/icons/lucide/puzzle-piece-fill.svg +1 -0
  31. package/icons/lucide/shield-check-fill.svg +1 -0
  32. package/icons/lucide/sidebar-simple.svg +1 -1
  33. package/icons/lucide/sidebar.svg +1 -0
  34. package/icons/lucide/square-half-bottom-fill.svg +1 -0
  35. package/icons/lucide/square-half.svg +1 -0
  36. package/icons/lucide/squares-four-fill.svg +1 -0
  37. package/icons/lucide/storefront-fill.svg +1 -0
  38. package/icons/lucide/tag-fill.svg +1 -0
  39. package/icons/lucide/toggle-right-fill.svg +1 -0
  40. package/icons/lucide/trash-fill.svg +1 -0
  41. package/icons/lucide/user-fill.svg +1 -0
  42. package/icons/lucide/wrench-fill.svg +1 -0
  43. package/icons/material-symbols/browser-fill.svg +1 -0
  44. package/icons/material-symbols/buildings-fill.svg +1 -0
  45. package/icons/material-symbols/certificate-fill.svg +1 -0
  46. package/icons/material-symbols/chart-line-up-fill.svg +1 -0
  47. package/icons/material-symbols/chats-fill.svg +1 -0
  48. package/icons/material-symbols/envelope-simple-fill.svg +1 -0
  49. package/icons/material-symbols/file-text-fill.svg +1 -0
  50. package/icons/material-symbols/folder-simple-fill.svg +1 -0
  51. package/icons/material-symbols/gauge.svg +1 -1
  52. package/icons/material-symbols/gear-six.svg +1 -0
  53. package/icons/material-symbols/globe-simple.svg +1 -0
  54. package/icons/material-symbols/heart-fill.svg +1 -0
  55. package/icons/material-symbols/layout.svg +1 -0
  56. package/icons/material-symbols/list-checks-fill.svg +1 -0
  57. package/icons/material-symbols/magnifying-glass-fill.svg +1 -0
  58. package/icons/material-symbols/megaphone-fill.svg +1 -0
  59. package/icons/material-symbols/newspaper-fill.svg +1 -0
  60. package/icons/material-symbols/paint-brush-broad-fill.svg +1 -0
  61. package/icons/material-symbols/puzzle-piece-fill.svg +1 -0
  62. package/icons/material-symbols/shield-check-fill.svg +1 -0
  63. package/icons/material-symbols/sidebar-simple.svg +1 -1
  64. package/icons/material-symbols/sidebar.svg +1 -0
  65. package/icons/material-symbols/square-half-bottom-fill.svg +1 -0
  66. package/icons/material-symbols/square-half.svg +1 -0
  67. package/icons/material-symbols/squares-four-fill.svg +1 -0
  68. package/icons/material-symbols/storefront-fill.svg +1 -0
  69. package/icons/material-symbols/tag-fill.svg +1 -0
  70. package/icons/material-symbols/toggle-right-fill.svg +1 -0
  71. package/icons/material-symbols/trash-fill.svg +1 -0
  72. package/icons/material-symbols/user-fill.svg +1 -0
  73. package/icons/material-symbols/wrench-fill.svg +1 -0
  74. package/icons/phosphor/browser-fill.svg +1 -0
  75. package/icons/phosphor/buildings-fill.svg +1 -0
  76. package/icons/phosphor/certificate-fill.svg +1 -0
  77. package/icons/phosphor/chart-line-up-fill.svg +1 -0
  78. package/icons/phosphor/chats-fill.svg +1 -0
  79. package/icons/phosphor/envelope-simple-fill.svg +1 -0
  80. package/icons/phosphor/file-text-fill.svg +1 -0
  81. package/icons/phosphor/folder-simple-fill.svg +1 -0
  82. package/icons/phosphor/gear-six.svg +1 -0
  83. package/icons/phosphor/globe-simple.svg +1 -0
  84. package/icons/phosphor/heart-fill.svg +1 -0
  85. package/icons/phosphor/layout.svg +1 -0
  86. package/icons/phosphor/list-checks-fill.svg +1 -0
  87. package/icons/phosphor/magnifying-glass-fill.svg +1 -0
  88. package/icons/phosphor/megaphone-fill.svg +1 -0
  89. package/icons/phosphor/newspaper-fill.svg +1 -0
  90. package/icons/phosphor/paint-brush-broad-fill.svg +1 -0
  91. package/icons/phosphor/puzzle-piece-fill.svg +1 -0
  92. package/icons/phosphor/shield-check-fill.svg +1 -0
  93. package/icons/phosphor/sidebar.svg +1 -0
  94. package/icons/phosphor/square-half-bottom-fill.svg +1 -0
  95. package/icons/phosphor/square-half.svg +1 -0
  96. package/icons/phosphor/squares-four-fill.svg +1 -0
  97. package/icons/phosphor/storefront-fill.svg +1 -0
  98. package/icons/phosphor/tag-fill.svg +1 -0
  99. package/icons/phosphor/toggle-right-fill.svg +1 -0
  100. package/icons/phosphor/trash-fill.svg +1 -0
  101. package/icons/phosphor/user-fill.svg +1 -0
  102. package/icons/phosphor/wrench-fill.svg +1 -0
  103. package/lib/breakpoints.d.mts +13 -0
  104. package/lib/breakpoints.mjs +20 -0
  105. package/lib/breakpoints.test.mjs +39 -0
  106. package/lib/build-lane.d.mts +15 -0
  107. package/lib/build-lane.mjs +23 -0
  108. package/lib/build-lane.test.mjs +74 -0
  109. package/lib/build-settings.d.mts +16 -0
  110. package/lib/build-settings.mjs +39 -0
  111. package/lib/build-settings.test.mjs +41 -0
  112. package/lib/contracts.d.mts +3 -2
  113. package/lib/contracts.mjs +3 -2
  114. package/lib/drawer-swipe.d.mts +10 -0
  115. package/lib/drawer-swipe.mjs +17 -0
  116. package/lib/drawer-swipe.test.mjs +33 -0
  117. package/lib/drawerKeys.d.mts +9 -0
  118. package/lib/drawerKeys.mjs +26 -0
  119. package/lib/drawerKeys.test.mjs +21 -0
  120. package/lib/eyebrow.d.mts +6 -0
  121. package/lib/eyebrow.mjs +24 -0
  122. package/lib/eyebrow.test.mjs +20 -0
  123. package/lib/front-sections.test.mjs +82 -0
  124. package/lib/heading-levels.d.mts +7 -0
  125. package/lib/heading-levels.mjs +16 -0
  126. package/lib/heading-levels.test.mjs +37 -0
  127. package/lib/inline.ts +3 -2
  128. package/lib/markdown.ts +2 -0
  129. package/lib/materialIcon.d.mts +21 -0
  130. package/lib/materialIcon.mjs +29 -0
  131. package/lib/materialIcon.test.mjs +45 -0
  132. package/lib/menu-layout.d.mts +11 -0
  133. package/lib/menu-layout.mjs +14 -0
  134. package/lib/menu-layout.test.mjs +23 -0
  135. package/lib/menu.d.mts +59 -0
  136. package/lib/menu.mjs +129 -0
  137. package/lib/menu.test.mjs +291 -0
  138. package/lib/rehype-emphasize.d.mts +4 -0
  139. package/lib/rehype-emphasize.mjs +20 -0
  140. package/lib/rehype-emphasize.test.mjs +39 -0
  141. package/lib/section-in-view.d.mts +8 -0
  142. package/lib/section-in-view.mjs +25 -0
  143. package/lib/section-in-view.test.mjs +35 -0
  144. package/lib/section-marker.d.mts +5 -0
  145. package/lib/section-marker.mjs +18 -0
  146. package/lib/section-marker.test.mjs +22 -0
  147. package/lib/section-ranges.d.mts +12 -0
  148. package/lib/section-ranges.mjs +43 -0
  149. package/lib/section-ranges.test.mjs +61 -0
  150. package/lib/sheet-stops.d.mts +68 -0
  151. package/lib/sheet-stops.mjs +198 -0
  152. package/lib/sheet-stops.test.mjs +155 -0
  153. package/lib/still-timer.d.mts +5 -0
  154. package/lib/still-timer.mjs +17 -0
  155. package/lib/still-timer.test.mjs +42 -0
  156. package/lib/text-sections.d.mts +10 -0
  157. package/lib/text-sections.mjs +45 -0
  158. package/lib/text-sections.test.mjs +78 -0
  159. package/lib/video.mjs +2 -1
  160. package/lib/without-h1.d.mts +1 -0
  161. package/lib/without-h1.mjs +7 -0
  162. package/lib/without-h1.test.mjs +26 -0
  163. package/lib/word-at.d.mts +6 -0
  164. package/lib/word-at.mjs +20 -0
  165. package/lib/word-at.test.mjs +19 -0
  166. package/package.json +3 -1
  167. package/pages/editor/version.json.js +22 -2
  168. package/scripts/already-checked.mjs +11 -0
  169. package/scripts/already-checked.test.mjs +18 -0
  170. package/scripts/build-settings-problems.mjs +27 -0
  171. package/scripts/build-settings-problems.test.mjs +35 -0
  172. package/scripts/check-content.mjs +42 -4
  173. package/scripts/check-worker.mjs +96 -4
  174. package/scripts/ci-build.mjs +85 -0
  175. package/scripts/ci-build.test.mjs +137 -0
  176. package/scripts/editor-roundtrip.mjs +62 -3
  177. package/scripts/editor-screens.mjs +21 -7
  178. package/scripts/git-env.mjs +6 -0
  179. package/scripts/step-command.mjs +34 -0
  180. package/scripts/step-command.test.mjs +50 -0
  181. package/scripts/text-problems.mjs +83 -0
  182. package/scripts/text-problems.test.mjs +74 -0
  183. package/scripts/worker-modules.mjs +11 -1
  184. package/site/breakpoints.d.mts +6 -0
  185. package/site/breakpoints.mjs +52 -0
  186. package/site/breakpoints.test.mjs +36 -0
  187. package/site/casing.d.mts +3 -0
  188. package/site/casing.mjs +26 -0
  189. package/site/content.config.ts +2 -0
  190. package/site/content.ts +2 -4
  191. package/site/defineSite.d.mts +1 -0
  192. package/site/defineSite.mjs +3 -0
  193. package/site/home.ts +3 -2
  194. package/site/icons.d.mts +116 -4
  195. package/site/icons.mjs +62 -6
  196. package/site/images.mjs +1 -1
  197. package/site/live-diff.d.mts +22 -0
  198. package/site/live-diff.mjs +98 -0
  199. package/site/live-diff.test.mjs +235 -0
  200. package/site/materialSymbols.d.mts +5 -0
  201. package/site/materialSymbols.mjs +19 -0
  202. package/site/sections.ts +1 -1
  203. package/site/theme.d.mts +1 -1
  204. package/site/theme.mjs +7 -1
  205. package/src/ActionParts.tsx +1 -1
  206. package/src/Advanced.tsx +101 -20
  207. package/src/App.tsx +47 -121
  208. package/src/AppList.tsx +1 -1
  209. package/src/BlockParts.tsx +3 -3
  210. package/src/Colors.tsx +1 -1
  211. package/src/CommunicationParts.tsx +1 -1
  212. package/src/ContainmentParts.tsx +80 -28
  213. package/src/Deleted.tsx +18 -4
  214. package/src/EditorFooter.tsx +14 -0
  215. package/src/EntryForm.tsx +75 -39
  216. package/src/FormFrame.tsx +3 -8
  217. package/src/Library.tsx +46 -51
  218. package/src/Licenses.tsx +27 -11
  219. package/src/MainDrawerParts.tsx +60 -0
  220. package/src/MainMenu.tsx +136 -0
  221. package/src/MarkdownEditor.tsx +175 -32
  222. package/src/Media.tsx +69 -16
  223. package/src/MediaTags.tsx +91 -0
  224. package/src/ModalSheetSample.tsx +47 -0
  225. package/src/NavigationParts.tsx +67 -21
  226. package/src/NavigationRestParts.tsx +1 -1
  227. package/src/Parts.tsx +71 -0
  228. package/src/PartsShown.tsx +1 -1
  229. package/src/PickerParts.tsx +2 -2
  230. package/src/Redirects.tsx +3 -2
  231. package/src/Screen.tsx +93 -90
  232. package/src/SelectionParts.tsx +22 -6
  233. package/src/SiteLogoLink.tsx +14 -0
  234. package/src/StartFab.tsx +22 -0
  235. package/src/StartStatus.tsx +1 -1
  236. package/src/autoTags.d.mts +28 -0
  237. package/src/autoTags.mjs +68 -0
  238. package/src/autoTags.test.mjs +109 -0
  239. package/src/buildSettings.test.mjs +20 -0
  240. package/src/buildSettings.ts +20 -0
  241. package/src/checkSite.ts +14 -1
  242. package/src/deploy.ts +7 -9
  243. package/src/editor.css +150 -17
  244. package/src/eyebrow.d.mts +5 -0
  245. package/src/eyebrow.mjs +53 -0
  246. package/src/eyebrowState.d.mts +21 -0
  247. package/src/eyebrowState.mjs +62 -0
  248. package/src/eyebrowState.test.mjs +78 -0
  249. package/src/fileRecords.d.mts +115 -0
  250. package/src/fileRecords.mjs +224 -0
  251. package/src/fileRecords.test.mjs +224 -0
  252. package/src/fileTagFilter.d.mts +30 -0
  253. package/src/fileTagFilter.mjs +44 -0
  254. package/src/fileTagFilter.test.mjs +57 -0
  255. package/src/fileTags.d.mts +56 -0
  256. package/src/fileTags.mjs +100 -0
  257. package/src/fileTags.test.mjs +127 -0
  258. package/src/firstHeading.d.mts +6 -0
  259. package/src/firstHeading.mjs +37 -0
  260. package/src/i18n/da-DK/advanced.ts +27 -12
  261. package/src/i18n/da-DK/app.ts +9 -7
  262. package/src/i18n/da-DK/apps.ts +16 -16
  263. package/src/i18n/da-DK/blockSettings.ts +9 -9
  264. package/src/i18n/da-DK/blocks.ts +2 -2
  265. package/src/i18n/da-DK/check.ts +5 -2
  266. package/src/i18n/da-DK/cms.ts +14 -12
  267. package/src/i18n/da-DK/colors.ts +5 -5
  268. package/src/i18n/da-DK/common.ts +2 -2
  269. package/src/i18n/da-DK/company.ts +3 -3
  270. package/src/i18n/da-DK/consents.ts +6 -6
  271. package/src/i18n/da-DK/forms.ts +6 -6
  272. package/src/i18n/da-DK/history.ts +6 -6
  273. package/src/i18n/da-DK/keywords.ts +2 -2
  274. package/src/i18n/da-DK/library.ts +17 -15
  275. package/src/i18n/da-DK/licenses.ts +4 -4
  276. package/src/i18n/da-DK/media.ts +10 -4
  277. package/src/i18n/da-DK/parts.ts +62 -25
  278. package/src/i18n/da-DK/preview.ts +5 -6
  279. package/src/i18n/da-DK/redirects.ts +7 -7
  280. package/src/i18n/da-DK/seo.ts +1 -1
  281. package/src/i18n/da-DK/text.ts +2 -0
  282. package/src/i18n/da-DK/theme.ts +5 -5
  283. package/src/i18n/en-US/advanced.ts +34 -18
  284. package/src/i18n/en-US/app.ts +7 -5
  285. package/src/i18n/en-US/apps.ts +9 -9
  286. package/src/i18n/en-US/blockSettings.ts +4 -4
  287. package/src/i18n/en-US/blocks.ts +6 -6
  288. package/src/i18n/en-US/check.ts +6 -3
  289. package/src/i18n/en-US/cms.ts +15 -14
  290. package/src/i18n/en-US/colors.ts +18 -18
  291. package/src/i18n/en-US/common.ts +3 -3
  292. package/src/i18n/en-US/consents.ts +4 -4
  293. package/src/i18n/en-US/forms.ts +5 -5
  294. package/src/i18n/en-US/history.ts +2 -2
  295. package/src/i18n/en-US/library.ts +21 -15
  296. package/src/i18n/en-US/licenses.ts +6 -6
  297. package/src/i18n/en-US/media.ts +7 -1
  298. package/src/i18n/en-US/parts.ts +60 -21
  299. package/src/i18n/en-US/preview.ts +5 -6
  300. package/src/i18n/en-US/redirects.ts +3 -3
  301. package/src/i18n/en-US/seo.ts +1 -1
  302. package/src/i18n/en-US/text.ts +3 -0
  303. package/src/i18n/en-US/theme.ts +6 -6
  304. package/src/icon.tsx +90 -27
  305. package/src/licenseData.test.mjs +28 -0
  306. package/src/licenseData.ts +12 -1
  307. package/src/markdown.d.mts +2 -0
  308. package/src/markdown.mjs +73 -5
  309. package/src/markdown.test.mjs +74 -0
  310. package/src/materialIcons.ts +8 -0
  311. package/src/menuChildren.ts +36 -0
  312. package/src/menuOpen.ts +34 -0
  313. package/src/pagePreview.tsx +23 -19
  314. package/src/r2Caching.ts +14 -0
  315. package/src/sectionFrame.ts +39 -0
  316. package/src/sectionState.d.mts +19 -0
  317. package/src/sectionState.mjs +31 -0
  318. package/src/sectionState.test.mjs +59 -0
  319. package/src/sections.d.mts +9 -7
  320. package/src/sections.mjs +74 -27
  321. package/src/sections.test.mjs +59 -0
  322. package/src/sidebar.ts +2 -2
  323. package/src/site.ts +27 -17
  324. package/src/stillCursor.d.mts +8 -0
  325. package/src/stillCursor.mjs +19 -0
  326. package/src/stillCursor.test.mjs +26 -0
  327. package/src/tagUses.d.mts +47 -0
  328. package/src/tagUses.mjs +121 -0
  329. package/src/tagUses.test.mjs +141 -0
  330. package/src/themeUi.ts +10 -0
  331. package/src/topBarSlot.tsx +37 -0
  332. package/src/ui.tsx +654 -150
  333. package/src/virtual.d.ts +5 -0
  334. package/starter/README.md +2 -1
  335. package/starter/package.json +2 -1
  336. package/starter/wrangler.jsonc +3 -2
  337. package/theme/blocks/cards/LinkCards.tsx +2 -1
  338. package/theme/blocks/hero/View.tsx +2 -1
  339. package/theme/blocks/video/View.tsx +2 -1
  340. package/theme/components/BlogIndex.astro +2 -2
  341. package/theme/components/BlogPosts.astro +1 -1
  342. package/theme/components/BlogPosts.tsx +9 -6
  343. package/theme/components/Container.astro +1 -1
  344. package/theme/components/CookieConsent.astro +1 -1
  345. package/theme/components/EndBlock.astro +1 -1
  346. package/theme/components/Footer.astro +5 -5
  347. package/theme/components/Header.astro +3 -3
  348. package/theme/components/Icon.astro +2 -2
  349. package/theme/components/MobileMenu.tsx +72 -61
  350. package/theme/components/MobileNav.astro +2 -2
  351. package/theme/components/PageHeader.astro +2 -2
  352. package/theme/components/PageSection.astro +1 -1
  353. package/theme/components/PopupButton.tsx +2 -2
  354. package/theme/components/PopupView.tsx +2 -2
  355. package/theme/components/PostList.tsx +7 -3
  356. package/theme/components/RichText.astro +22 -7
  357. package/theme/components/Section.astro +17 -10
  358. package/theme/components/SectionHeading.tsx +1 -1
  359. package/theme/components/home/PageBlock.astro +4 -4
  360. package/theme/components/home/Text.astro +3 -3
  361. package/theme/layouts/PageLayout.astro +3 -3
  362. package/theme/layouts/PostLayout.astro +9 -8
  363. package/theme/styles/base.css +13 -3
  364. package/theme/styles/blocks.css +97 -10
  365. package/theme/styles/components.css +9 -9
  366. package/theme/styles/elevation.test.mjs +168 -0
  367. package/theme/styles/global.css +65 -59
  368. package/theme/styles/ui.css +460 -131
  369. package/theme/templates/Home.astro +1 -1
  370. package/theme/templates/New.astro +5 -5
  371. package/theme/templates/NotFound.astro +2 -2
  372. package/theme/ui/BottomSheet.tsx +118 -21
  373. package/theme/ui/BottomSheetResizeIcon.tsx +13 -0
  374. package/theme/ui/Button.tsx +4 -3
  375. package/theme/ui/Dialog.tsx +1 -1
  376. package/theme/ui/FabMenu.tsx +13 -12
  377. package/theme/ui/LoadingIndicator.tsx +1 -1
  378. package/theme/ui/NavigationDrawer.tsx +116 -15
  379. package/theme/ui/NavigationRail.tsx +22 -13
  380. package/theme/ui/Search.tsx +1 -1
  381. package/theme/ui/SettingRow.tsx +31 -0
  382. package/theme/ui/Switch.tsx +11 -11
  383. package/theme/ui/TextField.tsx +17 -6
  384. package/theme/ui/bottomSheet.script.ts +3 -2
  385. package/theme/ui/bottomSheetControl.ts +131 -46
  386. package/theme/ui/fabMenu.script.ts +69 -53
  387. package/theme/ui/modalSheetSize.ts +214 -0
  388. package/theme/ui/navigationDrawerControl.ts +35 -0
  389. package/theme/ui/search.script.ts +4 -2
  390. package/worker/index.js +3 -0
  391. package/worker/plan-status.js +46 -0
  392. package/worker/push.js +7 -5
  393. package/worker/r2-status.js +2 -1
  394. package/icons/lucide/desktop.svg +0 -1
  395. package/icons/material-symbols/desktop.svg +0 -1
  396. package/icons/phosphor/desktop.svg +0 -1
package/src/virtual.d.ts CHANGED
@@ -1,3 +1,8 @@
1
+ declare module 'virtual:material-symbols-ssr' {
2
+ const files: Record<string, string>;
3
+ export default files;
4
+ }
5
+
1
6
  // What is in each app's folder (appFoldersPlugin in lib/apps.mjs): its files, by their paths in it, and its csp.json.
2
7
  declare module 'virtual:app-folders' {
3
8
  const folders: Record<string, { files: string[]; csp?: Partial<Record<string, string[]>> }>;
package/starter/README.md CHANGED
@@ -25,4 +25,5 @@ npm run dev
25
25
  ```
26
26
 
27
27
  `npm run check` kører tjekkene (formatering, typer, indholdet efter editorens regler, editoren og Workeren), før hver
28
- push og før hver bygning på Cloudflare, og `npm run build` bygger sitet i `dist/`.
28
+ push. Cloudflare bygger med `npx morpheme ci-build`: alle tjekkene, eller kun editorens egne, når kun indhold er ændret
29
+ siden det live site; og `npm run build` bygger sitet i `dist/`.
@@ -29,9 +29,10 @@
29
29
  "@astrojs/rss": "^4.0.19",
30
30
  "@astrojs/sitemap": "^3.7.4",
31
31
  "@fontsource-variable/roboto": "^5.3.0",
32
+ "@material-symbols/svg-400": "0.48.2",
32
33
  "@material/material-color-utilities": "^0.3.0",
33
34
  "@milkdown/kit": "^7.22.3",
34
- "@morphemecms/cms": "0.5.0",
35
+ "@morphemecms/cms": "0.7.0",
35
36
  "@phosphor-icons/core": "^2.1.1",
36
37
  "@phosphor-icons/react": "^2.1.10",
37
38
  "@tailwindcss/typography": "^0.5.20",
@@ -22,9 +22,10 @@
22
22
  "d1_databases": [{ "binding": "CONSENT" }],
23
23
  // Builds of other branches than main run `wrangler preview`, which needs this block.
24
24
  "previews": {},
25
- // The checks first, so a branch that fails them is neither built nor deployed.
25
+ // morpheme ci-build runs the checks first (all of them, or just the editor's when only content changed since the live
26
+ // commit), so a branch that fails them is neither built nor deployed.
26
27
  "build": {
27
- "command": "npm run check && npm run build",
28
+ "command": "npx morpheme ci-build",
28
29
  },
29
30
  // Each hour, the pages and posts that are Planlagt are published once their time has come, and the apps do theirs. Each
30
31
  // minute, whether the site has been built with a save, for the notification on the phone.
@@ -4,11 +4,12 @@
4
4
  // theme's surface or (`background`) the accent: the standard block "Kort", and a site's own cards (this site's front
5
5
  // page).
6
6
  // `className`: the site's own classes on the list, e.g. its margin.
7
+ import { BREAKPOINTS } from '@morphemecms/cms/lib/breakpoints.mjs';
7
8
  import { Svg } from '@morphemecms/cms/src/icon';
8
9
  import type { LinkCard } from '@morphemecms/cms/src/standard/cards/view';
9
10
  import Card from '@theme/ui/Card';
10
11
 
11
- const sizes = '(min-width: 640px) 352px, 100vw';
12
+ const sizes = `(min-width: ${BREAKPOINTS.medium}px) 352px, 100vw`;
12
13
 
13
14
  export default function LinkCards({
14
15
  items,
@@ -7,12 +7,13 @@
7
7
  // screen may show them (data-hero-cta, Base.astro).
8
8
  import Buttons from '@theme/blocks/buttons/Buttons';
9
9
  import type { HeroView } from '@morphemecms/cms/src/standard/hero/view';
10
+ import { BREAKPOINTS } from '@morphemecms/cms/lib/breakpoints.mjs';
10
11
  import { useInText } from '@morphemecms/cms/src/standard/view';
11
12
 
12
13
  export default function Hero({ view }: { view: HeroView }) {
13
14
  const { image } = view;
14
15
  // The widths the picture is shown in: behind the text the whole block's, beside it a part of it.
15
- const sizes = view.behind ? (view.full ? '100vw' : '(min-width: 1152px) 1152px, 100vw') : '(min-width: 768px) 40vw, 100vw';
16
+ const sizes = view.behind ? (view.full ? '100vw' : 'min(72rem, 100vw)') : `(min-width: ${BREAKPOINTS.expanded}px) 40vw, 100vw`;
16
17
  return (
17
18
  <div
18
19
  className={useInText('block-hero', view.behind && 'block-hero-behind', !image && 'block-hero-plain', view.full && 'block-hero-full')}
@@ -3,6 +3,7 @@
3
3
  // its title; a click puts the video in its place (Base.astro). A new link is a new video, with its own still. In the
4
4
  // editor a still the site does not have yet is YouTube's or Vimeo's, else the plain box.
5
5
  import { useState } from 'react';
6
+ import { BREAKPOINTS } from '@morphemecms/cms/lib/breakpoints.mjs';
6
7
  import { Svg } from '@morphemecms/cms/src/icon';
7
8
  import type { VideoView } from '@morphemecms/cms/src/standard/video/view';
8
9
  import { InProse, Link, useOnSite } from '@morphemecms/cms/src/standard/view';
@@ -26,7 +27,7 @@ function Facade({ view }: { view: VideoView }) {
26
27
  className="video-facade-image"
27
28
  src={still.src}
28
29
  srcSet={still.srcset}
29
- sizes={still.srcset && '(min-width: 768px) 720px, 100vw'}
30
+ sizes={still.srcset && `(min-width: ${BREAKPOINTS.expanded}px) 720px, 100vw`}
30
31
  alt=""
31
32
  loading="lazy"
32
33
  decoding="async"
@@ -24,8 +24,8 @@ const categories = await getCategories();
24
24
 
25
25
  <Base {...seo} title={title} description={description} crumbs={activeCategory ? [{ name: 'Blog', href: '/blog/' }] : []}>
26
26
  <PageHeader title={title} intro={intro}>
27
- <nav aria-label={siteT.theme.tags} class="-mx-5 mt-8 md:mx-0">
28
- <ul class="flex gap-2 overflow-x-auto px-5 pb-2 [scrollbar-width:none] md:flex-wrap md:overflow-visible md:px-0">
27
+ <nav aria-label={siteT.theme.tags} class="-mx-5 mt-8 medium:mx-0">
28
+ <ul class="flex gap-2 overflow-x-auto px-5 pb-2 [scrollbar-width:none] medium:flex-wrap medium:overflow-visible medium:px-0">
29
29
  <li class="shrink-0">
30
30
  <Chip kind="filter" href="/blog/" label={siteT.theme.allPosts} selected={!activeCategory} className="shrink-0" />
31
31
  </li>
@@ -13,6 +13,6 @@ interface Props {
13
13
  const { posts, class: className } = Astro.props;
14
14
  ---
15
15
 
16
- <Container class={className ?? (posts.length ? 'mt-10 md:mt-14' : 'mt-10')}>
16
+ <Container class={className ?? (posts.length ? 'mt-10 medium:mt-14' : 'mt-10')}>
17
17
  <BlogPostsView posts={posts.map(postViewOf)} />
18
18
  </Container>
@@ -6,6 +6,7 @@ import PostList from '@theme/components/PostList';
6
6
  import PostMeta from '@theme/components/PostMeta';
7
7
  import type { PostView } from '@theme/components/posts';
8
8
  import { Link } from '@morphemecms/cms/src/standard/view';
9
+ import { BREAKPOINTS } from '@morphemecms/cms/lib/breakpoints.mjs';
9
10
  import { siteT } from '@morphemecms/cms/src/i18n';
10
11
 
11
12
  export default function BlogPosts({ posts }: { posts: PostView[] }) {
@@ -21,25 +22,27 @@ export default function BlogPosts({ posts }: { posts: PostView[] }) {
21
22
  );
22
23
  return (
23
24
  <>
24
- <Link href={featured.href} className="group grid items-center gap-6 md:grid-cols-12 md:gap-12">
25
+ <Link href={featured.href} className="group grid items-center gap-6 medium:gap-12 expanded:grid-cols-12">
25
26
  {featured.image && (
26
- <div className="overflow-hidden rounded-(--theme-picture-radius) md:col-span-7">
27
+ <div className="overflow-hidden rounded-(--theme-picture-radius) expanded:col-span-7">
27
28
  <Img
28
29
  image={featured.image}
29
30
  alt={featured.imageAlt ?? ''}
30
31
  fetchPriority="high"
31
32
  className="aspect-[16/10] w-full object-cover [transition:scale_var(--motion-spatial-slow)] group-hover:scale-[1.03]"
32
- sizes="(min-width: 768px) 55vw, calc(100vw - 40px)"
33
+ sizes={`(min-width: ${BREAKPOINTS.expanded}px) 55vw, calc(100vw - 40px)`}
33
34
  />
34
35
  </div>
35
36
  )}
36
- <div className="md:col-span-5">
37
+ <div className="expanded:col-span-5">
37
38
  <PostMeta post={featured} />
38
- <h2 className="mt-2 type-headline-medium transition-colors group-hover:text-accent md:type-headline-large">{featured.title}</h2>
39
+ <h2 className="mt-2 type-headline-medium transition-colors group-hover:text-accent medium:type-headline-large">
40
+ {featured.title}
41
+ </h2>
39
42
  <p className="mt-3 text-muted">{featured.description}</p>
40
43
  </div>
41
44
  </Link>
42
- {rest.length > 0 && <PostList posts={rest} className="mt-12 md:mt-16" />}
45
+ {rest.length > 0 && <PostList posts={rest} className="mt-12 medium:mt-16" />}
43
46
  </>
44
47
  );
45
48
  }
@@ -6,6 +6,6 @@ type Props = HTMLAttributes<'section'> & { as?: 'section' | 'div' | 'header' };
6
6
  const { as: Tag = 'section', class: className, ...rest } = Astro.props;
7
7
  ---
8
8
 
9
- <Tag class:list={['mx-auto max-w-6xl px-5 md:px-8', className]} {...rest}>
9
+ <Tag class:list={['mx-auto max-w-6xl px-5 medium:px-8', className]} {...rest}>
10
10
  <slot />
11
11
  </Tag>
@@ -34,7 +34,7 @@ const necessary = category('necessary');
34
34
  hidden
35
35
  class:list={[
36
36
  dialogClass,
37
- 'fixed inset-x-3 bottom-3 z-[42] max-h-[calc(100dvh-1.5rem)] max-w-md overflow-y-auto p-5 md:inset-x-auto md:bottom-6 md:left-6 md:p-6',
37
+ 'fixed inset-x-3 bottom-3 z-[42] max-h-[calc(100dvh-1.5rem)] max-w-md overflow-y-auto p-5 medium:inset-x-auto medium:bottom-6 medium:left-6 medium:p-6',
38
38
  ]}
39
39
  style="margin-bottom: env(safe-area-inset-bottom)"
40
40
  >
@@ -16,6 +16,6 @@ interface Props {
16
16
  const { type, props = {}, ...rest } = Astro.props;
17
17
  ---
18
18
 
19
- <Container class="block-place mt-20 md:mt-28" data-reveal {...rest}>
19
+ <Container class="block-place mt-20 medium:mt-28" data-reveal {...rest}>
20
20
  <Block type={type} props={props} />
21
21
  </Container>
@@ -21,13 +21,13 @@ const contactLinks = [
21
21
  ---
22
22
 
23
23
  <footer class="md-footer">
24
- <Container as="div" class="grid gap-10 py-12 md:grid-cols-12 md:py-16">
25
- <div class="md:col-span-5">
24
+ <Container as="div" class="grid gap-10 py-12 expanded:grid-cols-12 medium:py-16">
25
+ <div class="expanded:col-span-5">
26
26
  <LogoMark class="block h-12 w-11 bg-accent" />
27
27
  <p class="md-footer-name">{site.name}</p>
28
28
  <p class="md-footer-text">{footer.text}</p>
29
29
  </div>
30
- <div class="md:col-span-4">
30
+ <div class="expanded:col-span-4">
31
31
  <p class="md-footer-heading">{footer.contactHeading}</p>
32
32
  <ul class="md-footer-list">
33
33
  {contactLinks.map((c) => (
@@ -47,7 +47,7 @@ const contactLinks = [
47
47
  ))}
48
48
  </ul>
49
49
  </div>
50
- <div class="md-footer-column md:col-span-3">
50
+ <div class="md-footer-column expanded:col-span-3">
51
51
  <slot name="footer-column">
52
52
  <p class="md-footer-heading">{footer.linksHeading}</p>
53
53
  <ul class="md-footer-list">
@@ -61,7 +61,7 @@ const contactLinks = [
61
61
  </ul>
62
62
  </slot>
63
63
  </div>
64
- <div class="md-footer-bottom md:col-span-12">
64
+ <div class="md-footer-bottom expanded:col-span-12">
65
65
  <span>
66
66
  © {new Date().getFullYear()} {site.name}
67
67
  {site.cvr && <> · CVR {site.cvr}</>}
@@ -26,7 +26,7 @@ const fab = !current.startsWith(site.bookingPath) && current !== '/tak/';
26
26
  <header data-header class:list={[topAppBarClass, 'group/header sticky top-0 z-[43]']}>
27
27
  <Container as="div" data-header-row class="flex h-16 items-center gap-1">
28
28
  {navShown && (
29
- <div class="-ml-2 mr-1 xl:hidden xl:group-data-crowded/header:block">
29
+ <div class="-ml-2 mr-1 large:hidden large:group-data-crowded/header:block">
30
30
  <MobileMenu
31
31
  client:interact
32
32
  items={nav}
@@ -48,12 +48,12 @@ const fab = !current.startsWith(site.bookingPath) && current !== '/tak/';
48
48
  />
49
49
  </div>
50
50
  )}
51
- <a href="/" class="md-top-app-bar-title mr-auto xl:shrink-0 xl:whitespace-nowrap">
51
+ <a href="/" class="md-top-app-bar-title mr-auto large:shrink-0 large:whitespace-nowrap">
52
52
  <Logo />
53
53
  </a>
54
54
 
55
55
  {navShown && (
56
- <nav aria-label={siteT.theme.mainMenu} class="hidden shrink-0 xl:block xl:group-data-crowded/header:hidden">
56
+ <nav aria-label={siteT.theme.mainMenu} class="hidden shrink-0 large:block large:group-data-crowded/header:hidden">
57
57
  <ul class="flex items-center gap-1">
58
58
  {nav.map((item, i) => (
59
59
  <li class="group relative flex items-center" data-nav-item>
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  // An icon by its name, drawn by the same Svg as the blocks and the editor (@morphemecms/cms/src/icon): the theme's own
3
- // (icons/<name>.svg), else the icon pack in use's, else Phosphor's; one the site does not draw as the question mark
4
- // (MISSING), which check:screens finds on the built pages.
3
+ // (icons/<name>.svg), else the icon pack in use's, else Phosphor's, else Material Symbols' by its Material name (listed by
4
+ // check:screens); one none has as the question mark (MISSING), which check:screens finds on the built pages.
5
5
  import { Svg } from '@morphemecms/cms/src/icon';
6
6
 
7
7
  interface Props {
@@ -9,16 +9,18 @@
9
9
  import { useEffect, useRef, useState } from 'react';
10
10
  import { createPortal } from 'react-dom';
11
11
  import { AnimatePresence, motion, useReducedMotion } from 'motion/react';
12
- import { Svg } from '@morphemecms/cms/src/icon';
13
12
  import { motionSpring } from '@morphemecms/cms/lib/motion';
14
13
  import IconButton from '@theme/ui/IconButton';
15
14
  import { ExtendedFab } from '@theme/ui/Fab';
16
15
  import {
17
16
  NavigationDrawerItem,
18
17
  NavigationDrawerRow,
18
+ NavigationDrawerSection,
19
19
  NavigationDrawerSubitem,
20
20
  NavigationDrawerToggle,
21
+ focusNavigationDrawerItem,
21
22
  navigationDrawerClass,
23
+ navigationDrawerKeys,
22
24
  navigationDrawerScrimClass,
23
25
  } from '@theme/ui/NavigationDrawer';
24
26
 
@@ -54,7 +56,6 @@ export default function MobileMenu({
54
56
  const [mounted, setMounted] = useState(false);
55
57
  const reduce = useReducedMotion();
56
58
  const openButton = useRef<HTMLButtonElement>(null);
57
- const closeButton = useRef<HTMLButtonElement>(null);
58
59
  const drawer = useRef<HTMLDivElement>(null);
59
60
  // The scheme's springs (only used in the browser, where the menu opens): in at the default spatial spring, out at the
60
61
  // fast one, and the scrim with the effects'.
@@ -65,14 +66,21 @@ export default function MobileMenu({
65
66
  fadeOut: motionSpring('effects', 'fast'),
66
67
  }));
67
68
 
68
- useEffect(() => setMounted(true), []);
69
+ // The edge it comes from is the start edge: it slides in from the left, and from the right in right-to-left languages.
70
+ const [away, setAway] = useState('-100%');
71
+ useEffect(() => {
72
+ setMounted(true);
73
+ setAway(getComputedStyle(document.documentElement).direction === 'rtl' ? '100%' : '-100%');
74
+ }, []);
69
75
 
70
76
  useEffect(() => {
71
77
  if (!open) return;
72
78
  const root = document.documentElement;
73
79
  const prevOverflow = root.style.overflow;
74
80
  root.style.overflow = 'hidden';
75
- closeButton.current?.focus();
81
+ // The focus goes to the first item; the arrow keys, Home and End move between them.
82
+ if (drawer.current) focusNavigationDrawerItem(drawer.current);
83
+ const stopKeys = drawer.current ? navigationDrawerKeys(drawer.current) : undefined;
76
84
 
77
85
  const onKey = (e: KeyboardEvent) => {
78
86
  if (e.key === 'Escape') setOpen(false);
@@ -92,6 +100,7 @@ export default function MobileMenu({
92
100
  };
93
101
  document.addEventListener('keydown', onKey);
94
102
  return () => {
103
+ stopKeys?.();
95
104
  document.removeEventListener('keydown', onKey);
96
105
  // Closed by "Book en tid": the booking sheet (a modal dialog) is open now and keeps the page
97
106
  // still and the focus itself.
@@ -135,10 +144,10 @@ export default function MobileMenu({
135
144
  role="dialog"
136
145
  aria-modal="true"
137
146
  aria-label={texts.drawer}
138
- className={`${navigationDrawerClass} absolute inset-y-0 left-0`}
139
- initial={reduce ? false : { x: '-100%' }}
147
+ className={`${navigationDrawerClass} absolute inset-y-0 start-0`}
148
+ initial={reduce ? false : { x: away }}
140
149
  animate={{ x: 0, transition: reduce ? { duration: 0 } : spring.enter }}
141
- exit={reduce ? undefined : { x: '-100%', transition: spring.leave }}
150
+ exit={reduce ? undefined : { x: away, transition: spring.leave }}
142
151
  >
143
152
  <div className="md-drawer-head">
144
153
  <span aria-hidden="true" className="logo-mark h-10 w-9 shrink-0 bg-accent" />
@@ -146,7 +155,7 @@ export default function MobileMenu({
146
155
  <p className="md-drawer-name">{name}</p>
147
156
  <p className="md-drawer-role">{role}</p>
148
157
  </div>
149
- <IconButton ref={closeButton} icon="x" label={texts.close} onClick={() => setOpen(false)} />
158
+ <IconButton icon="x" label={texts.close} onClick={() => setOpen(false)} />
150
159
  </div>
151
160
 
152
161
  <ExtendedFab
@@ -158,61 +167,63 @@ export default function MobileMenu({
158
167
  onClick={() => setOpen(false)}
159
168
  />
160
169
 
161
- <nav aria-label={texts.menu}>
162
- <ul>
163
- {items.map((item, i) => (
164
- <li key={item.href}>
165
- <NavigationDrawerRow data-active={holdsCurrent(item) || undefined}>
166
- <NavigationDrawerItem
167
- href={item.href}
168
- onClick={() => setOpen(false)}
169
- aria-current={current === item.href ? 'page' : undefined}
170
- >
171
- {item.label}
172
- </NavigationDrawerItem>
173
- {item.children && (
174
- <NavigationDrawerToggle
175
- label={texts.subpages[i]}
176
- expanded={expanded === item.href}
177
- onClick={() => setExpanded(expanded === item.href ? null : item.href)}
178
- aria-controls={`sub-${item.href}`}
179
- />
180
- )}
181
- </NavigationDrawerRow>
182
- <AnimatePresence initial={false}>
183
- {item.children && expanded === item.href && (
184
- <motion.ul
185
- id={`sub-${item.href}`}
186
- className="overflow-hidden"
187
- initial={reduce ? false : { height: 0, opacity: 0 }}
188
- animate={{ height: 'auto', opacity: 1, transition: spring.enter }}
189
- exit={reduce ? undefined : { height: 0, opacity: 0, transition: spring.leave }}
170
+ <NavigationDrawerSection>
171
+ <nav aria-label={texts.menu}>
172
+ <ul>
173
+ {items.map((item, i) => (
174
+ <li key={item.href}>
175
+ <NavigationDrawerRow data-active={holdsCurrent(item) || undefined}>
176
+ <NavigationDrawerItem
177
+ href={item.href}
178
+ onClick={() => setOpen(false)}
179
+ aria-current={current === item.href ? 'page' : undefined}
190
180
  >
191
- {item.children.map((child) => (
192
- <li key={child.href}>
193
- <NavigationDrawerSubitem
194
- href={child.href}
195
- active={current === child.href}
196
- onClick={() => setOpen(false)}
197
- aria-current={current === child.href ? 'page' : undefined}
198
- >
199
- {child.label}
200
- </NavigationDrawerSubitem>
201
- </li>
202
- ))}
203
- </motion.ul>
204
- )}
205
- </AnimatePresence>
206
- </li>
207
- ))}
208
- </ul>
209
- </nav>
181
+ {item.label}
182
+ </NavigationDrawerItem>
183
+ {item.children && (
184
+ <NavigationDrawerToggle
185
+ label={texts.subpages[i]}
186
+ expanded={expanded === item.href}
187
+ onClick={() => setExpanded(expanded === item.href ? null : item.href)}
188
+ aria-controls={`sub-${item.href}`}
189
+ />
190
+ )}
191
+ </NavigationDrawerRow>
192
+ <AnimatePresence initial={false}>
193
+ {item.children && expanded === item.href && (
194
+ <motion.ul
195
+ id={`sub-${item.href}`}
196
+ className="overflow-hidden"
197
+ initial={reduce ? false : { height: 0, opacity: 0 }}
198
+ animate={{ height: 'auto', opacity: 1, transition: spring.enter }}
199
+ exit={reduce ? undefined : { height: 0, opacity: 0, transition: spring.leave }}
200
+ >
201
+ {item.children.map((child) => (
202
+ <li key={child.href}>
203
+ <NavigationDrawerSubitem
204
+ href={child.href}
205
+ active={current === child.href}
206
+ onClick={() => setOpen(false)}
207
+ aria-current={current === child.href ? 'page' : undefined}
208
+ >
209
+ {child.label}
210
+ </NavigationDrawerSubitem>
211
+ </li>
212
+ ))}
213
+ </motion.ul>
214
+ )}
215
+ </AnimatePresence>
216
+ </li>
217
+ ))}
218
+ </ul>
219
+ </nav>
220
+ </NavigationDrawerSection>
210
221
 
211
- <hr className="md-divider" />
212
- <NavigationDrawerItem href={phoneHref}>
213
- <Svg name="phone" size={24} />
214
- {call} {phone}
215
- </NavigationDrawerItem>
222
+ <NavigationDrawerSection>
223
+ <NavigationDrawerItem href={phoneHref} icon="phone">
224
+ {call} {phone}
225
+ </NavigationDrawerItem>
226
+ </NavigationDrawerSection>
216
227
  </motion.div>
217
228
  </div>
218
229
  )}
@@ -13,8 +13,8 @@ const isActive = (href: string) => current === href || (href !== '/' && current.
13
13
  ---
14
14
 
15
15
  {mobileNav.length > 0 && (
16
- <nav aria-label={siteT.theme.mobileMenu} data-mobile-nav class="xl:hidden xl:group-data-crowded/header:block">
17
- <ul class:list={[tabsClass, 'relative mx-auto max-w-6xl px-1 md:px-4']}>
16
+ <nav aria-label={siteT.theme.mobileMenu} data-mobile-nav class="large:hidden large:group-data-crowded/header:block">
17
+ <ul class:list={[tabsClass, 'relative mx-auto max-w-6xl px-1 medium:px-4']}>
18
18
  {mobileNav.map((item) => (
19
19
  <li>
20
20
  <a href={item.href} class={tabClass} aria-current={isActive(item.href) ? 'page' : undefined}>
@@ -11,10 +11,10 @@ interface Props {
11
11
  const { title, intro } = Astro.props;
12
12
  ---
13
13
 
14
- <Container as="header" class="pt-8 md:pt-14">
14
+ <Container as="header" class="pt-8 medium:pt-14">
15
15
  <h1 class="theme-title max-w-[18ch]">{title}</h1>
16
16
  {intro && (
17
- <p class="type-body-large md:type-title-large mt-4 max-w-[52ch] text-muted">
17
+ <p class="type-body-large medium:type-title-large mt-4 max-w-[52ch] text-muted">
18
18
  <Inline text={intro} />
19
19
  </p>
20
20
  )}
@@ -14,7 +14,7 @@ const { title, intro, class: className, id } = Astro.props;
14
14
  ---
15
15
 
16
16
  <Container class={className} id={id} data-reveal>
17
- {title && <h2 class="type-headline-large md:type-display-small">{title}</h2>}
17
+ {title && <h2 class="type-headline-large medium:type-display-small">{title}</h2>}
18
18
  {intro && (
19
19
  <p class="mt-3 max-w-[52ch] text-muted">
20
20
  <Inline text={intro} />
@@ -13,11 +13,11 @@ export default function PopupButton({ button }: { button?: PopupButtonView }) {
13
13
  return (
14
14
  <div className="block-buttons mt-6">
15
15
  {'file' in button ? (
16
- <Button href={button.file.href} icon="download-simple" download="" className="w-full sm:w-auto">
16
+ <Button href={button.file.href} icon="download-simple" download="" className="w-full medium:w-auto">
17
17
  {button.file.label}
18
18
  </Button>
19
19
  ) : (
20
- <Buttons buttons={button.buttons} className="w-full sm:w-auto" />
20
+ <Buttons buttons={button.buttons} className="w-full medium:w-auto" />
21
21
  )}
22
22
  </div>
23
23
  );
@@ -27,12 +27,12 @@ export default function PopupView({ popup, Part, part }: { popup: PopupBoxView;
27
27
  srcSet={popup.image.srcset}
28
28
  width={popup.image.width}
29
29
  height={popup.image.height}
30
- sizes="(min-width: 480px) 448px, calc(100vw - 2rem)"
30
+ sizes="min(28rem, calc(100vw - 2rem))"
31
31
  alt=""
32
32
  className="block aspect-[3/2] w-full bg-tint object-cover"
33
33
  />
34
34
  )}
35
- <div className="p-6 md:p-8">
35
+ <div className="p-6 medium:p-8">
36
36
  <h2 id={`popup-${popup.slug}`} className={popup.image ? 'type-headline-small' : 'pr-12 type-headline-small'}>
37
37
  {popup.heading}
38
38
  </h2>
@@ -4,6 +4,7 @@
4
4
  import Img from '@theme/components/Img';
5
5
  import PostMeta from '@theme/components/PostMeta';
6
6
  import type { PostView } from '@theme/components/posts';
7
+ import { BREAKPOINTS } from '@morphemecms/cms/lib/breakpoints.mjs';
7
8
  import { Link, useOnSite } from '@morphemecms/cms/src/standard/view';
8
9
 
9
10
  // Shows the number in data-random-posts of the posts in the list just before it, picked at random.
@@ -29,16 +30,19 @@ export default function PostList({
29
30
  const onSite = useOnSite();
30
31
  return (
31
32
  <>
32
- <ul className={['grid gap-x-12 md:grid-cols-2', className].filter(Boolean).join(' ')} data-random-posts={random ? count : undefined}>
33
+ <ul
34
+ className={['grid gap-x-12 expanded:grid-cols-2', className].filter(Boolean).join(' ')}
35
+ data-random-posts={random ? count : undefined}
36
+ >
33
37
  {posts.map((post, i) => (
34
38
  <li key={post.href || post.title} hidden={random && i >= count} className="border-t border-line">
35
- <Link href={post.href} className="group grid grid-cols-[5.5rem_1fr] gap-4 py-5 sm:grid-cols-[8rem_1fr] sm:gap-5">
39
+ <Link href={post.href} className="group grid grid-cols-[5.5rem_1fr] gap-4 py-5 medium:grid-cols-[8rem_1fr] medium:gap-5">
36
40
  {post.image ? (
37
41
  <Img
38
42
  image={post.image}
39
43
  alt=""
40
44
  className="theme-post-picture aspect-square w-full object-cover"
41
- sizes="(min-width: 640px) 128px, 88px"
45
+ sizes={`(min-width: ${BREAKPOINTS.medium}px) 128px, 88px`}
42
46
  />
43
47
  ) : (
44
48
  <span className="theme-post-picture aspect-square bg-tint" />
@@ -5,8 +5,9 @@
5
5
  // is in it, as a video written in the text, packages/editor/lib/video.mjs); a block hidden in the editor is left
6
6
  // out (splitBlocks). The HTML comes as `html`, or as the default slot (rendered Markdown). `path`: the
7
7
  // page's address, for the testimonials' label. `preview`: a new version of a page (/ny/), whose topics
8
- // show the opening of the new versions too. Each block gets its type, and its shared texts as `texts` (blockTexts).
8
+ // show the opening of the new versions too. A section with a colour is drawn as <section class="text-section text-section-<color>"> around its parts (lib/text-sections.mjs). Each block gets its type, and its shared texts as `texts` (blockTexts).
9
9
  import { splitBlocks } from '@morphemecms/cms/lib/blocks.mjs';
10
+ import { sectionClass, sectionsOf } from '@morphemecms/cms/lib/text-sections.mjs';
10
11
  import { blockSites } from '@morphemecms/cms/src/standard/sites';
11
12
  import { blockTexts } from '@morphemecms/cms/site/site';
12
13
 
@@ -14,15 +15,29 @@ interface Props {
14
15
  html?: string;
15
16
  path?: string;
16
17
  preview?: boolean;
18
+ // false: the text only, its blocks left as written (a blog post's text, which draws none).
19
+ blocks?: boolean;
17
20
  }
18
- const { path, preview } = Astro.props;
21
+ const { path, preview, blocks = true } = Astro.props;
19
22
  const html = Astro.props.html ?? (await Astro.slots.render('default'));
20
23
 
21
- const parts = splitBlocks(html, Object.keys(blockSites));
24
+ // A section with a colour (lib/text-sections.mjs) is a run of its own, drawn as a panel around its parts (blocks.css).
25
+ let n = 0;
26
+ const runs = sectionsOf(html).map((run) => ({
27
+ color: run.color,
28
+ parts: splitBlocks(run.html, blocks ? Object.keys(blockSites) : []).map((p) => ({ ...p, index: n++ })),
29
+ }));
22
30
  ---
23
31
 
24
- {parts.map((p, i) => {
25
- if (p.html !== undefined) return <Fragment set:html={p.html} />;
26
- const Block = blockSites[p.type];
27
- return <Block type={p.type} props={p.props} index={i} path={path} preview={preview} texts={blockTexts[p.type]} />;
32
+ {runs.map((run) => {
33
+ const Run = run.color ? 'section' : Fragment;
34
+ return (
35
+ <Run {...(run.color ? { class: sectionClass(run.color) } : {})}>
36
+ {run.parts.map((p) => {
37
+ if (p.html !== undefined) return <Fragment set:html={p.html} />;
38
+ const Block = blockSites[p.type];
39
+ return <Block type={p.type} props={p.props} index={p.index} path={path} preview={preview} texts={blockTexts[p.type]} />;
40
+ })}
41
+ </Run>
42
+ );
28
43
  })}