@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
@@ -1,5 +1,6 @@
1
1
  ---
2
- // A front-page section: container, spacing above, reveal on scroll and an optional heading + intro.
2
+ // A front-page section: container, spacing above, reveal on scroll and an optional heading + intro; with a `color`
3
+ // the content sits in a panel (.text-section-<color>, blocks.css) inside the container, none without one.
3
4
  import Container from '@theme/components/Container.astro';
4
5
  import SectionHeading from '@theme/components/SectionHeading';
5
6
  import Inline from '@theme/components/Inline.astro';
@@ -10,20 +11,26 @@ interface Props {
10
11
  class?: string;
11
12
  /** Id for the heading; the section is then labelled by it (aria-labelledby). */
12
13
  id?: string;
14
+ /** The colour of the panel behind the section (`sectionColor` from the front page's heading, as in a page's text-section). */
15
+ color?: string;
13
16
  }
14
- const { title, intro, class: className, id } = Astro.props;
17
+ const { title, intro, class: className, id, color } = Astro.props;
18
+ const Panel = color ? 'div' : Fragment;
19
+ const panel = color ? { class: `text-section text-section-${color} my-0` } : {};
15
20
  ---
16
21
 
17
22
  <Container
18
- class:list={['pt-(--theme-section-space) md:pt-(--theme-section-space-large)', className]}
23
+ class:list={['pt-(--theme-section-space) medium:pt-(--theme-section-space-large)', className]}
19
24
  aria-labelledby={title && id ? id : undefined}
20
25
  data-reveal
21
26
  >
22
- {title && <SectionHeading id={id} text={title} />}
23
- {intro && (
24
- <p class="type-body-large mt-3 max-w-[52ch] text-muted">
25
- <Inline text={intro} />
26
- </p>
27
- )}
28
- <slot />
27
+ <Panel {...panel}>
28
+ {title && <SectionHeading id={id} text={title} />}
29
+ {intro && (
30
+ <p class="type-body-large mt-3 max-w-[52ch] text-muted">
31
+ <Inline text={intro} />
32
+ </p>
33
+ )}
34
+ <slot />
35
+ </Panel>
29
36
  </Container>
@@ -1,7 +1,7 @@
1
1
  // The h2 used for every front-page section. `text`: its text.
2
2
  export default function SectionHeading({ text, id, className }: { text: string; id?: string; className?: string }) {
3
3
  return (
4
- <h2 id={id} className={['type-headline-large md:type-display-medium', className].filter(Boolean).join(' ')}>
4
+ <h2 id={id} className={['type-headline-large medium:type-display-medium', className].filter(Boolean).join(' ')}>
5
5
  {text}
6
6
  </h2>
7
7
  );
@@ -1,18 +1,18 @@
1
1
  ---
2
2
  // A block a page can have, as a section of the front page (`pageBlocks` in packages/editor/defaults/collections.ts): the standard
3
- // one's or the app's (Block.astro, by its type), in the front page's width and space, with its shared texts.
3
+ // one's or the app's (Block.astro, by its type), in the front page's width and space, with its shared texts; a colour under its heading (`sectionColor`) goes to Section, not to the block.
4
4
  import Block from '@morphemecms/cms/src/standard/Block.astro';
5
5
  import Section from '@theme/components/Section.astro';
6
6
  import { blockTexts } from '@morphemecms/cms/site/site';
7
7
 
8
8
  interface Props {
9
- section: { type: string; hidden?: boolean } & Record<string, unknown>;
9
+ section: { type: string; hidden?: boolean; sectionColor?: string } & Record<string, unknown>;
10
10
  class?: string;
11
11
  }
12
- const { type, hidden, ...props } = Astro.props.section;
12
+ const { type, hidden, sectionColor, ...props } = Astro.props.section;
13
13
  ---
14
14
 
15
- <Section class={Astro.props.class}>
15
+ <Section class={Astro.props.class} color={sectionColor}>
16
16
  <div class="block-place">
17
17
  <Block type={type} props={props} texts={blockTexts[type]} />
18
18
  </div>
@@ -1,13 +1,13 @@
1
1
  ---
2
- // A text the site's owner writes: an optional heading and Markdown.
2
+ // A text the site's owner writes: an optional heading and Markdown, in a panel when its heading has a colour (`sectionColor`).
3
3
  import Section from '@theme/components/Section.astro';
4
4
  import Markdown from '@theme/components/Markdown.astro';
5
5
  import type { HomeSection } from '@morphemecms/cms/site/home';
6
6
 
7
7
  type Props = Extract<HomeSection, { type: 'text' }> & { class?: string };
8
- const { heading, body, class: className } = Astro.props;
8
+ const { heading, body, sectionColor, class: className } = Astro.props;
9
9
  ---
10
10
 
11
- <Section title={heading} class={className}>
11
+ <Section title={heading} class={className} color={sectionColor}>
12
12
  <Markdown body={body} class={heading && 'mt-6'} />
13
13
  </Section>
@@ -90,7 +90,7 @@ const side =
90
90
  questions={questions}
91
91
  >
92
92
  <article>
93
- <Container as="header" class="pt-8 md:pt-14">
93
+ <Container as="header" class="pt-8 medium:pt-14">
94
94
  {parent && (
95
95
  <a href={parent.href} class="inline-flex items-center gap-1.5 type-body-medium text-muted hover:text-accent">
96
96
  <Icon name="arrow-left" size={16} />
@@ -102,14 +102,14 @@ const side =
102
102
  </h1>
103
103
  </Container>
104
104
 
105
- <Container as="div" class:list={[wide ? 'mt-4' : 'mt-8 md:mt-12']}>
105
+ <Container as="div" class:list={[wide ? 'mt-4' : 'mt-8 medium:mt-12']}>
106
106
  <div {...(side ? { class: 'page-with-side' } : {})}>
107
107
  <div
108
108
  data-editor="text"
109
109
  class:list={[
110
110
  'prose',
111
111
  wide
112
- ? 'max-w-none [&>:not(.not-prose)]:max-w-[66ch] [&>p:first-child]:max-w-[52ch] [&>p:first-child]:type-body-large [&>p:first-child]:text-muted md:[&>p:first-child]:type-title-large'
112
+ ? 'max-w-none [&>:not(.not-prose)]:max-w-[66ch] [&>p:first-child]:max-w-[52ch] [&>p:first-child]:type-body-large [&>p:first-child]:text-muted medium:[&>p:first-child]:type-title-large'
113
113
  : 'max-w-[66ch]',
114
114
  ]}
115
115
  >
@@ -1,12 +1,13 @@
1
1
  ---
2
2
  // Blog post template. A narrow reading column, the site's owner as author; related posts in the footer.
3
3
  // data-editor marks what the editor's preview fills in with what is in its form ("Forhåndsvis",
4
- // packages/editor/src/pagePreview.tsx): the heading, the picture and the text.
4
+ // packages/editor/src/pagePreview.tsx): the heading, the picture and the text. The text goes through RichText (blocks={false}: a post draws no blocks, but its coloured sections are panels).
5
5
  import { firstImage, heroImage, picturesOf } from '@morphemecms/cms/site/images.mjs';
6
6
  import Base from '@theme/layouts/Base.astro';
7
7
  import Icon from '@theme/components/Icon.astro';
8
8
  import Img from '@theme/components/Img.astro';
9
9
  import EndBlock from '@theme/components/EndBlock.astro';
10
+ import RichText from '@theme/components/RichText.astro';
10
11
  import { categoryName, getPosts, postPath, stateOf, type Post } from '@morphemecms/cms/site/content';
11
12
  import PostMeta from '@theme/components/PostMeta';
12
13
  import Chip from '@theme/ui/Chip';
@@ -68,7 +69,7 @@ const related = (await getPosts())
68
69
  ]}
69
70
  >
70
71
  <article>
71
- <header class="mx-auto max-w-3xl px-5 pt-8 md:px-8 md:pt-14">
72
+ <header class="mx-auto max-w-3xl px-5 pt-8 medium:px-8 medium:pt-14">
72
73
  <a href="/blog/" class="inline-flex items-center gap-1.5 type-body-medium text-muted hover:text-accent">
73
74
  <Icon name="arrow-left" size={16} /> {siteT.theme.blog}
74
75
  </a>
@@ -87,22 +88,22 @@ const related = (await getPosts())
87
88
  </header>
88
89
 
89
90
  {image && (
90
- <div data-editor="image" class="mx-auto mt-8 max-w-5xl px-5 md:mt-12 md:px-8">
91
+ <div data-editor="image" class="mx-auto mt-8 max-w-5xl px-5 medium:mt-12 medium:px-8">
91
92
  <Img
92
93
  src={image}
93
94
  alt={imageAlt ?? ''}
94
95
  fetchpriority="high"
95
- class="aspect-[16/10] w-full rounded-(--theme-picture-radius) object-cover md:aspect-[16/9]"
96
- sizes="(min-width: 1024px) 960px, calc(100vw - 40px)"
96
+ class="aspect-[16/10] w-full rounded-(--theme-picture-radius) object-cover medium:aspect-[16/9]"
97
+ sizes="min(60rem, calc(100vw - 2.5rem))"
97
98
  />
98
99
  </div>
99
100
  )}
100
101
 
101
- <div data-editor="text" class="prose mx-auto mt-10 max-w-3xl px-5 md:mt-14 md:px-8">
102
- <slot />
102
+ <div data-editor="text" class="prose mx-auto mt-10 max-w-3xl px-5 medium:mt-14 medium:px-8">
103
+ <RichText html={await Astro.slots.render('default')} blocks={false} />
103
104
  </div>
104
105
 
105
- <footer class="mx-auto mt-12 max-w-3xl px-5 md:px-8">
106
+ <footer class="mx-auto mt-12 max-w-3xl px-5 medium:px-8">
106
107
  {onBlog.length > 0 && (
107
108
  <ul class="flex flex-wrap gap-2" aria-label="Tags">
108
109
  {onBlog.map((c) => (
@@ -93,6 +93,16 @@
93
93
  --ease-soft: var(--ease);
94
94
  }
95
95
 
96
+ /* Material 3's breakpoints (m3.material.io/foundations/layout/breakpoints): Tailwind's medium:, expanded:, large: and
97
+ extra-large: (and max-…:), and theme(--breakpoint-…) in a @media; the same as lib/breakpoints.mjs for scripts. */
98
+ @theme {
99
+ --breakpoint-*: initial;
100
+ --breakpoint-medium: 37.5rem;
101
+ --breakpoint-expanded: 52.5rem;
102
+ --breakpoint-large: 75rem;
103
+ --breakpoint-extra-large: 100rem;
104
+ }
105
+
96
106
  /* z-index scale (documented, no ad-hoc values): action bar 30, header 40, cookie banner 42 (under the
97
107
  open menu, which must stay usable), menu 45, skip link 50 */
98
108
 
@@ -276,7 +286,7 @@
276
286
  font-weight: var(--type-headline-large-weight);
277
287
  letter-spacing: var(--type-headline-large-tracking);
278
288
  }
279
- @media (min-width: 48rem) {
289
+ @media (width >= theme(--breakpoint-medium)) {
280
290
  .block-place > h2,
281
291
  .block-place > .block-head > h2 {
282
292
  font-size: var(--type-display-medium-size);
@@ -287,7 +297,7 @@
287
297
  }
288
298
  /* Cards placed in the page's full width (a front-page section, PageBlock.astro): three in a row on a computer, as
289
299
  there is room for (in the text, two). */
290
- @media (min-width: 64rem) {
300
+ @media (width >= theme(--breakpoint-expanded)) {
291
301
  .block-place .block-cards {
292
302
  grid-template-columns: repeat(3, minmax(0, 1fr));
293
303
  }
@@ -313,7 +323,7 @@
313
323
  font-weight: var(--type-headline-small-weight);
314
324
  letter-spacing: var(--type-headline-small-tracking);
315
325
  }
316
- @media (min-width: 48rem) {
326
+ @media (width >= theme(--breakpoint-medium)) {
317
327
  .block-panels > .block-panel {
318
328
  --card-padding: 2rem;
319
329
  }
@@ -23,6 +23,9 @@
23
23
  square, a step on a time line)
24
24
  --block-control-radius the corners of a button and a tag (a pill when large)
25
25
  --block-shadow the shadow of a raised panel
26
+ --block-section-padding, --block-section-margin the space inside, and above and below, a coloured section of a text
27
+ (.text-section: a panel the width of the text column, on --block-surface, --block-tint or --block-accent; on the accent
28
+ the blocks in it get inverted variables: text, accent and line on-accent, surface and tint a veil of it)
26
29
  --block-ease the easing of motion (else the motion scheme's, --motion-ease)
27
30
  --block-ease-quick the easing of a quick change of state (a colour, an arrow that turns or moves; else the motion
28
31
  scheme's, --motion-ease-quick)
@@ -31,7 +34,7 @@
31
34
  --block-page-width, --block-gutter the page's container (its width, gutters included, and its gutter at the
32
35
  side), whose left edge the text starts at: how far a banner across the whole width reaches out
33
36
  of the text to the window's edges, and where its content starts again (the standard theme's
34
- Container.astro: 72rem, 1.25rem and 2rem from 48rem)
37
+ Container.astro: 72rem, 1.25rem and 2rem from the medium width)
35
38
 
36
39
  How things move is the site's motion scheme (Material 3's, chosen under "Udseende" › "Bevægelse"; lib/motion.ts), which
37
40
  Base.astro writes into the <head> as springs, each its duration and curve, for `transition` and `animation`:
@@ -81,8 +84,10 @@
81
84
  --block-ease-quick: var(--motion-ease-quick, cubic-bezier(0.2, 0, 0, 1));
82
85
  --block-page-width: 72rem;
83
86
  --block-gutter: 1.25rem;
87
+ --block-section-padding: 1.5rem;
88
+ --block-section-margin: 2rem;
84
89
  }
85
- @media (min-width: 48rem) {
90
+ @media (width >= theme(--breakpoint-medium)) {
86
91
  :where(:root) {
87
92
  --block-gutter: 2rem;
88
93
  }
@@ -95,6 +100,62 @@
95
100
  margin-block: 2rem;
96
101
  }
97
102
 
103
+ /* A coloured section of a page's or post's text (lib/text-sections.mjs, RichText.astro): a panel the width of the text
104
+ column, from its heading to the next (its first and last parts' margins: at the end of the file). */
105
+ .text-section {
106
+ margin-block: var(--block-section-margin);
107
+ border-radius: var(--block-radius);
108
+ padding: var(--block-section-padding);
109
+ }
110
+ .text-section-surface {
111
+ background: var(--block-surface);
112
+ box-shadow: var(--block-shadow);
113
+ }
114
+ .text-section-tint {
115
+ background: var(--block-tint);
116
+ }
117
+ /* On the accent everything in the text is the text-on-accent colour (the prose colours are variables, .prose in base.css),
118
+ links too: they are underlined by the prose style. The section's real colours are kept in private variables first
119
+ (--section-…): the blocks in it get the variables swapped (below), and a variable cannot swap with another on the
120
+ same element. */
121
+ .text-section-accent {
122
+ --section-accent: var(--block-accent);
123
+ --section-on-accent: var(--block-on-accent);
124
+ --section-text: var(--block-text);
125
+ --section-surface: var(--block-surface);
126
+ background: var(--section-accent);
127
+ color: var(--section-on-accent);
128
+ --tw-prose-body: var(--section-on-accent);
129
+ --tw-prose-headings: var(--section-on-accent);
130
+ --tw-prose-lead: var(--section-on-accent);
131
+ --tw-prose-links: var(--section-on-accent);
132
+ --tw-prose-bold: var(--section-on-accent);
133
+ --tw-prose-counters: var(--section-on-accent);
134
+ --tw-prose-bullets: var(--section-on-accent);
135
+ --tw-prose-hr: var(--section-on-accent);
136
+ --tw-prose-quotes: var(--section-on-accent);
137
+ --tw-prose-quote-borders: var(--section-on-accent);
138
+ --tw-prose-captions: var(--section-on-accent);
139
+ --tw-prose-code: var(--section-on-accent);
140
+ }
141
+ /* The blocks in it read as an inverted theme on the accent: text, lines and the accent itself are the text-on-accent
142
+ colour, a surface is a veil of it, and what was text-on-accent (a filled button's label, a step number) is the
143
+ accent. Dark mode follows, as the variables do. */
144
+ .text-section-accent > * {
145
+ --block-text: var(--section-on-accent);
146
+ --block-muted: color-mix(in srgb, var(--section-on-accent) 80%, transparent);
147
+ --block-accent: var(--section-on-accent);
148
+ --block-accent-hover: color-mix(in srgb, var(--section-on-accent) 88%, var(--section-accent));
149
+ --block-on-accent: var(--section-accent);
150
+ --block-surface: color-mix(in srgb, var(--section-on-accent) 14%, transparent);
151
+ --block-tint: color-mix(in srgb, var(--section-on-accent) 14%, transparent);
152
+ --block-line: color-mix(in srgb, var(--section-on-accent) 35%, transparent);
153
+ --block-shadow: none;
154
+ /* A hero with its picture behind the text is darkened with the section's own text colour, in its own surface. */
155
+ --block-hero-shade: var(--section-text);
156
+ --block-hero-on-shade: var(--section-surface);
157
+ }
158
+
98
159
  /* "Besked": a short notice in a box with the accent along its left edge (notice/). */
99
160
  .block-notice {
100
161
  border-left: 4px solid var(--block-accent);
@@ -393,7 +454,7 @@
393
454
  body:has(.block-hero-full.block-in-text) {
394
455
  overflow-x: clip;
395
456
  }
396
- @media (min-width: 48rem) {
457
+ @media (width >= theme(--breakpoint-expanded)) {
397
458
  .block-hero {
398
459
  grid-template-columns: 7fr 5fr;
399
460
  gap: 3rem;
@@ -964,7 +1025,7 @@
964
1025
  font-weight: var(--type-title-large-weight);
965
1026
  letter-spacing: var(--type-title-large-tracking);
966
1027
  }
967
- @media (width >= 40rem) {
1028
+ @media (width >= theme(--breakpoint-medium)) {
968
1029
  .block-timeline {
969
1030
  grid-auto-columns: 1fr;
970
1031
  grid-auto-flow: column;
@@ -1000,7 +1061,7 @@
1000
1061
  padding-top: 1.25rem;
1001
1062
  }
1002
1063
  /* Only on phones and tablets (a block a site shows beside the text on a computer). */
1003
- @media (width >= 64rem) {
1064
+ @media (width >= theme(--breakpoint-expanded)) {
1004
1065
  .block-small-screens {
1005
1066
  display: none;
1006
1067
  }
@@ -1405,7 +1466,7 @@
1405
1466
  scale: 1.03;
1406
1467
  }
1407
1468
  }
1408
- @media (width >= 40rem) {
1469
+ @media (width >= theme(--breakpoint-medium)) {
1409
1470
  .block-buttons {
1410
1471
  flex-direction: row;
1411
1472
  flex-wrap: wrap;
@@ -1420,7 +1481,7 @@
1420
1481
  text-align: right;
1421
1482
  }
1422
1483
  }
1423
- @media (width >= 48rem) {
1484
+ @media (width >= theme(--breakpoint-medium)) {
1424
1485
  .block-testimonial {
1425
1486
  --card-padding: 1.75rem;
1426
1487
  }
@@ -1737,7 +1798,7 @@
1737
1798
  color: #fff;
1738
1799
  text-shadow: 0 1px 3px rgb(0 0 0 / 0.8);
1739
1800
  }
1740
- @media (min-width: 768px) {
1801
+ @media (width >= theme(--breakpoint-medium)) {
1741
1802
  .prose .video-facade-still svg {
1742
1803
  width: 4.5rem;
1743
1804
  height: 4.5rem;
@@ -1754,7 +1815,7 @@
1754
1815
  heading is the text's own <h2>, without this): the label in small capitals, spaced out, in the accent; the
1755
1816
  subheading larger and quiet. Mostly in the text's style (.prose), so outside the layers, like the video above, whose
1756
1817
  margins it sets in place of the text's; a theme that places a block outside the text sets its own (.block-place in
1757
- the standard theme). A theme gives it its own look outside the layers too (the standard theme: in the middle). */
1818
+ the standard theme). A label above a heading in a text has the same look. A theme gives it its own look outside the layers too (the standard theme: in the middle). */
1758
1819
  .block-head {
1759
1820
  margin-block: 2.5em 1.25em;
1760
1821
  }
@@ -1773,7 +1834,8 @@
1773
1834
  .block-head > .block-eyebrow + h2 {
1774
1835
  margin-top: 0.5rem;
1775
1836
  }
1776
- .block-head > .block-eyebrow {
1837
+ .block-head > .block-eyebrow,
1838
+ .prose .block-eyebrow {
1777
1839
  font-size: var(--type-label-medium-size);
1778
1840
  line-height: var(--type-label-medium-line-height);
1779
1841
  font-weight: var(--type-label-medium-weight);
@@ -1789,6 +1851,15 @@
1789
1851
  color: var(--block-muted);
1790
1852
  }
1791
1853
 
1854
+ /* A label above a heading in a text (packages/editor/lib/eyebrow.mjs): a block's label, with the heading's space above
1855
+ it and close to its heading. */
1856
+ .prose .block-eyebrow {
1857
+ margin-block: 2em 0;
1858
+ }
1859
+ .prose .block-eyebrow + :is(h1, h2, h3) {
1860
+ margin-top: 0.5rem;
1861
+ }
1862
+
1792
1863
  /* A word or more in one of the site's colours, in a text (packages/editor/lib/text-colors.mjs, the colour button in the
1793
1864
  editor): the accent, or the quiet text's, both readable on the background, the surfaces and the tint (the site's
1794
1865
  pairs of colours, checked under "Farver"). Bold, italic and a link in it have its colour. Outside the layers, as it
@@ -1806,3 +1877,19 @@
1806
1877
  .block-hero-behind :is(.color-accent, .color-muted) :is(strong, em, a) {
1807
1878
  color: inherit;
1808
1879
  }
1880
+
1881
+ /* A word in the accent's or the quiet text's colour (above) on a section with the accent as its background would not
1882
+ be read: there it has the section's text colour. */
1883
+ .text-section-accent :is(.color-accent, .color-muted),
1884
+ .text-section-accent :is(.color-accent, .color-muted) :is(strong, em, a) {
1885
+ color: inherit;
1886
+ }
1887
+
1888
+ /* The first and last parts of a coloured section lose the prose margins that would add to the panel's padding. Outside
1889
+ the layers, as the prose style (Tailwind's typography) is in the utilities layer, which wins over components. */
1890
+ .text-section > :first-child {
1891
+ margin-top: 0;
1892
+ }
1893
+ .text-section > :last-child {
1894
+ margin-bottom: 0;
1895
+ }
@@ -43,7 +43,7 @@
43
43
  --theme-title-leading: var(--type-display-small-line-height);
44
44
  --theme-title-size-large: var(--type-display-medium-size);
45
45
  }
46
- @media (min-width: 48rem) {
46
+ @media (width >= theme(--breakpoint-medium)) {
47
47
  :root {
48
48
  --theme-title-leading: var(--type-display-medium-line-height);
49
49
  }
@@ -87,7 +87,7 @@
87
87
  .theme-logo-role {
88
88
  display: var(--theme-logo-role-display);
89
89
  }
90
- @media (min-width: 48rem) {
90
+ @media (width >= theme(--breakpoint-medium)) {
91
91
  .theme-logo-role {
92
92
  display: var(--theme-logo-role-display-large);
93
93
  }
@@ -109,7 +109,7 @@
109
109
  /* The bar at the bottom of a phone's screen (a promotion: ActionBar.astro), a docked toolbar
110
110
  (.toolbar-docked, ui.css) fixed there: the page gets room under its footer, and only the first bar on a page is
111
111
  shown. data-hidden: moved down out of sight (while the buttons at the top of the front page are on screen, the script
112
- in Base.astro). Not from 48rem (a tablet in landscape, a computer). */
112
+ in Base.astro). Not from the medium width (a tablet in portrait, a computer). */
113
113
  .theme-action-bar {
114
114
  position: fixed;
115
115
  inset: auto 0 0;
@@ -122,12 +122,12 @@
122
122
  .theme-action-bar ~ .theme-action-bar {
123
123
  display: none;
124
124
  }
125
- @media (width < 48rem) {
125
+ @media (width < theme(--breakpoint-medium)) {
126
126
  body:has(.theme-action-bar) {
127
127
  padding-bottom: 6rem;
128
128
  }
129
129
  }
130
- @media (width >= 48rem) {
130
+ @media (width >= theme(--breakpoint-medium)) {
131
131
  .theme-action-bar {
132
132
  display: none;
133
133
  }
@@ -165,7 +165,7 @@
165
165
 
166
166
  @layer components {
167
167
  /* With the bar of "Mobilmenu" under the header (MobileNav.astro), a link to a part of a page stops below both. */
168
- @media (width < 80rem) {
168
+ @media (width < theme(--breakpoint-large)) {
169
169
  html:has([data-mobile-nav]) {
170
170
  scroll-padding-top: 9.25rem;
171
171
  }
@@ -193,12 +193,12 @@
193
193
  }
194
194
  }
195
195
 
196
- /* A page with a block beside its text (PageLayout.astro): the text, then the column (on a phone below the text); from md
196
+ /* A page with a block beside its text (PageLayout.astro): the text, then the column (on a phone below the text); from medium
197
197
  two columns, the second one staying in view while the text is read. */
198
198
  .page-with-side > .page-side {
199
199
  margin-top: 3rem;
200
200
  }
201
- @media (min-width: 48rem) {
201
+ @media (width >= theme(--breakpoint-medium)) {
202
202
  .page-with-side {
203
203
  display: grid;
204
204
  grid-template-columns: minmax(0, 1fr) var(--theme-side-width);
@@ -220,7 +220,7 @@
220
220
  font-weight: var(--type-display-small-weight);
221
221
  letter-spacing: var(--type-display-small-tracking);
222
222
  }
223
- @media (min-width: 48rem) {
223
+ @media (width >= theme(--breakpoint-medium)) {
224
224
  .theme-title {
225
225
  font-size: var(--theme-title-size-large);
226
226
  font-weight: var(--type-display-medium-weight);
@@ -0,0 +1,168 @@
1
+ // Checks the tonal elevation tokens and rules of the base theme (spec: tonal elevation, Material 3).
2
+ // Reads theme/styles/ui.css (and later global.css) as text; no browser, no build.
3
+ import { test } from 'node:test';
4
+ import assert from 'node:assert/strict';
5
+ import { readFileSync } from 'node:fs';
6
+
7
+ const read = (name) => readFileSync(new URL(name, import.meta.url), 'utf8');
8
+ const uiCss = read('./ui.css');
9
+
10
+ const count = (text, part) => text.split(part).length - 1;
11
+
12
+ // The level tokens: the exact default declaration of each, once in ui.css.
13
+ const tokens = {
14
+ '--elevation-0-surface': 'var(--bg)',
15
+ '--elevation-1-surface': 'var(--surface-container-low, var(--block-surface))',
16
+ '--elevation-2-surface': 'var(--surface-container, var(--block-surface))',
17
+ '--elevation-3-surface': 'var(--surface-container-high, var(--block-surface))',
18
+ '--elevation-4-surface': 'var(--surface-container-highest, var(--block-surface))',
19
+ '--elevation-5-surface': 'var(--surface-container-highest, var(--block-surface))',
20
+ '--elevation-1-rest-shadow': 'var(--elevation-1)',
21
+ '--elevation-2-rest-shadow': 'var(--elevation-2)',
22
+ '--elevation-3-rest-shadow': 'var(--elevation-3)',
23
+ '--elevation-4-rest-shadow': 'var(--elevation-4)',
24
+ '--elevation-5-rest-shadow': 'var(--elevation-5)',
25
+ };
26
+
27
+ for (const [name, value] of Object.entries(tokens)) {
28
+ test(`ui.css declares ${name}`, () => {
29
+ assert.equal(count(uiCss, ` ${name}: ${value};\n`), 1);
30
+ });
31
+ }
32
+
33
+ // Neutral parts: the resting surface reads the part's own variable, then its level's colour.
34
+ const surfaces = [
35
+ ['elevated button', 'background: var(--button-elevated-surface, var(--elevation-1-surface));', 1],
36
+ ['elevated chip', 'background: var(--chip-elevated-surface, var(--elevation-1-surface));', 1],
37
+ ['elevated card', 'background: var(--card-elevated-surface, var(--elevation-1-surface));', 1],
38
+ ['outlined card', 'background: var(--card-outlined-surface, var(--elevation-0-surface));', 1],
39
+ ['dialog (also date and time picker)', 'background: var(--dialog-surface, var(--elevation-3-surface));', 1],
40
+ ['date picker input label notch', '--text-field-label-surface: var(--dialog-surface, var(--elevation-3-surface));', 1],
41
+ ['bottom sheet (container and cover)', 'background: var(--bottom-sheet-surface, var(--elevation-1-surface));', 2],
42
+ ['standard side sheet', 'background: var(--side-sheet-surface, var(--elevation-0-surface));', 2],
43
+ ['modal side sheet', 'background: var(--side-sheet-modal-surface, var(--side-sheet-surface, var(--elevation-1-surface)));', 2],
44
+ ['scrolled top app bar', 'background: var(--top-app-bar-stuck-surface, var(--elevation-2-surface));', 1],
45
+ ['menu, menu group and select list', 'background: var(--menu-surface, var(--elevation-2-surface));', 3],
46
+ ['navigation drawer', 'background: var(--navigation-drawer-surface, var(--elevation-1-surface));', 1],
47
+ ['rich tooltip', 'background: var(--tooltip-rich-surface, var(--elevation-2-surface));', 1],
48
+ ['standard navigation rail', 'background: var(--navigation-rail-surface, var(--elevation-0-surface));', 1],
49
+ ['modal navigation rail', 'background: var(--navigation-rail-modal-surface, var(--elevation-2-surface));', 1],
50
+ ['search view', 'background: var(--search-view-surface, var(--elevation-3-surface));', 1],
51
+ ];
52
+
53
+ for (const [part, line, times] of surfaces) {
54
+ test(`${part} reads its level's surface`, () => {
55
+ assert.equal(count(uiCss, line), times);
56
+ });
57
+ }
58
+
59
+ // Parts that keep their surface: the top app bar at rest, and the role parts.
60
+ const unchanged = [
61
+ 'background: var(--top-app-bar-surface, color-mix(in oklab, var(--bg) 85%, transparent));',
62
+ 'background: var(--search-bar-surface, var(--block-surface));',
63
+ 'background: var(--navigation-bar-surface, var(--block-surface));',
64
+ 'background: var(--card-filled-surface, var(--block-tint));',
65
+ 'background: var(--switch-handle-surface, var(--block-surface));',
66
+ ];
67
+
68
+ for (const line of unchanged) {
69
+ test(`ui.css keeps ${line}`, () => {
70
+ assert.equal(count(uiCss, line), 1);
71
+ });
72
+ }
73
+
74
+ test('no part reads the old surface fallbacks', () => {
75
+ assert.equal(count(uiCss, 'var(--menu-surface, var(--block-surface))'), 0);
76
+ assert.equal(count(uiCss, 'var(--dialog-surface, var(--block-surface))'), 0);
77
+ assert.equal(count(uiCss, 'var(--navigation-drawer-surface, var(--surface-container-low, var(--bg)))'), 0);
78
+ assert.equal(count(uiCss, 'var(--navigation-rail-surface, var(--block-surface))'), 0);
79
+ });
80
+
81
+ // Resting shadows: the part's own variable first, then its level's rest-shadow token.
82
+ const restShadows = [
83
+ ['elevated button', 'box-shadow: var(--button-elevated-shadow, var(--elevation-1-rest-shadow));', 2],
84
+ ['elevated chip', 'box-shadow: var(--chip-elevated-shadow, var(--elevation-1-rest-shadow));', 2],
85
+ ['elevated card', '--card-shadow: var(--card-elevated-shadow, var(--elevation-1-rest-shadow));', 1],
86
+ ['modal side sheet (container and wide layout)', 'box-shadow: var(--side-sheet-shadow, var(--elevation-1-rest-shadow));', 2],
87
+ ['bottom sheet', 'box-shadow: var(--bottom-sheet-shadow, var(--elevation-1-rest-shadow));', 1],
88
+ ['bottom sheet as a dialog', 'box-shadow: var(--bottom-sheet-shadow-large, var(--elevation-3-rest-shadow));', 1],
89
+ ['dialog (also date and time picker)', 'var(--dialog-shadow, var(--elevation-3-rest-shadow));', 1],
90
+ ['docked date picker', 'var(--date-picker-docked-shadow, var(--dialog-shadow, var(--elevation-3-rest-shadow)));', 1],
91
+ ['scrolled top app bar', 'box-shadow: var(--top-app-bar-stuck-shadow, var(--elevation-2-rest-shadow));', 1],
92
+ ['modal navigation rail', 'box-shadow: var(--navigation-rail-modal-shadow, var(--elevation-2-rest-shadow));', 1],
93
+ ['navigation drawer', 'box-shadow: var(--navigation-drawer-shadow, var(--elevation-1-rest-shadow));', 1],
94
+ ['menu, menu group and select list', 'var(--menu-shadow, var(--elevation-2-rest-shadow));', 3],
95
+ ['rich tooltip', 'var(--tooltip-rich-shadow, var(--elevation-2-rest-shadow));', 1],
96
+ ['search view', 'var(--search-view-shadow, var(--elevation-3-rest-shadow));', 1],
97
+ ['floating toolbar', 'var(--toolbar-floating-shadow, var(--elevation-3-rest-shadow));', 1],
98
+ ['FAB and FAB menu', 'box-shadow: var(--fab-shadow, var(--elevation-3-rest-shadow));', 2],
99
+ ['snackbar', 'box-shadow: var(--snackbar-shadow, var(--elevation-3-rest-shadow));', 1],
100
+ ['switch handle', 'box-shadow: var(--switch-handle-shadow, var(--elevation-1-rest-shadow));', 1],
101
+ ['slider handle (both directions)', 'box-shadow: var(--slider-handle-shadow, var(--elevation-1-rest-shadow));', 2],
102
+ ];
103
+
104
+ for (const [part, line, times] of restShadows) {
105
+ test(`${part} reads its level's rest shadow`, () => {
106
+ assert.equal(count(uiCss, line), times);
107
+ });
108
+ }
109
+
110
+ // The lift stays on --elevation-N.
111
+ test('elevated card hover still reads --elevation-2', () => {
112
+ assert.equal(count(uiCss, '--card-shadow: var(--card-elevated-hover-shadow, var(--card-elevated-shadow, var(--elevation-2)));'), 1);
113
+ });
114
+
115
+ test('outlined card keeps --elevation-0', () => {
116
+ assert.equal(count(uiCss, '--card-shadow: var(--card-outlined-shadow, var(--elevation-0));'), 1);
117
+ });
118
+
119
+ // The Material theme (global.css): colour at rest, a shadow only for the FAB and the lift.
120
+ const globalCss = read('./global.css');
121
+
122
+ for (let level = 1; level <= 5; level++) {
123
+ test(`global.css sets --elevation-${level}-rest-shadow to no shadow`, () => {
124
+ assert.equal(count(globalCss, ` --elevation-${level}-rest-shadow: var(--elevation-0);\n`), 1);
125
+ });
126
+ }
127
+
128
+ const addedLines = [
129
+ ['--fab-shadow: var(--elevation-3);', 1],
130
+ ['--fab-hover-shadow: var(--elevation-4);', 1],
131
+ ['--block-shadow: var(--elevation-1-rest-shadow);', 1],
132
+ // Light mode once, then the dark-mode media block and the dark theme attribute.
133
+ ['--shadow: var(--elevation-1-rest-shadow);', 3],
134
+ ];
135
+
136
+ for (const [line, times] of addedLines) {
137
+ test(`global.css has ${line} ${times} time(s)`, () => {
138
+ assert.equal(count(globalCss, line), times);
139
+ });
140
+ }
141
+
142
+ const removedDeclarations = [
143
+ '--button-elevated-surface',
144
+ '--card-elevated-surface',
145
+ '--side-sheet-surface',
146
+ '--side-sheet-modal-surface',
147
+ '--bottom-sheet-surface',
148
+ '--dialog-surface',
149
+ '--top-app-bar-stuck-surface',
150
+ '--top-app-bar-stuck-shadow',
151
+ '--navigation-rail-surface',
152
+ '--navigation-drawer-surface',
153
+ '--menu-surface',
154
+ '--tooltip-rich-surface',
155
+ '--search-view-surface',
156
+ '--switch-handle-shadow',
157
+ '--slider-handle-shadow',
158
+ ];
159
+
160
+ for (const name of removedDeclarations) {
161
+ test(`global.css no longer declares ${name}`, () => {
162
+ assert.equal(count(globalCss, `${name}:`), 0);
163
+ });
164
+ }
165
+
166
+ test('global.css keeps the extended FAB lines in the navigation drawer', () => {
167
+ assert.equal(count(globalCss, ' --fab-shadow: var(--elevation-1);\n --fab-hover-shadow: var(--elevation-2);\n'), 1);
168
+ });