react-cheminfo 0.7.0 → 0.9.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 (308) hide show
  1. package/README.md +60 -2
  2. package/bin/check-tokens.mjs +105 -0
  3. package/lib/about/core/about.d.ts +95 -0
  4. package/lib/about/core/about.d.ts.map +1 -0
  5. package/lib/about/core/about.js +78 -0
  6. package/lib/about/core/about.js.map +1 -0
  7. package/lib/about/core/index.d.ts +3 -0
  8. package/lib/about/core/index.d.ts.map +1 -0
  9. package/lib/about/core/index.js +2 -0
  10. package/lib/about/core/index.js.map +1 -0
  11. package/lib/about/ui/AboutCitations.d.ts +14 -0
  12. package/lib/about/ui/AboutCitations.d.ts.map +1 -0
  13. package/lib/about/ui/AboutCitations.js +40 -0
  14. package/lib/about/ui/AboutCitations.js.map +1 -0
  15. package/lib/about/ui/AboutPage.d.ts +32 -0
  16. package/lib/about/ui/AboutPage.d.ts.map +1 -0
  17. package/lib/about/ui/AboutPage.js +70 -0
  18. package/lib/about/ui/AboutPage.js.map +1 -0
  19. package/lib/about/ui/AboutSection.d.ts +23 -0
  20. package/lib/about/ui/AboutSection.d.ts.map +1 -0
  21. package/lib/about/ui/AboutSection.js +28 -0
  22. package/lib/about/ui/AboutSection.js.map +1 -0
  23. package/lib/about/ui/index.d.ts +7 -0
  24. package/lib/about/ui/index.d.ts.map +1 -0
  25. package/lib/about/ui/index.js +4 -0
  26. package/lib/about/ui/index.js.map +1 -0
  27. package/lib/chrome/ui/index.d.ts +1 -0
  28. package/lib/chrome/ui/index.d.ts.map +1 -1
  29. package/lib/chrome/ui/index.js +3 -0
  30. package/lib/chrome/ui/index.js.map +1 -1
  31. package/lib/citation/core/index.d.ts +1 -0
  32. package/lib/citation/core/index.d.ts.map +1 -1
  33. package/lib/citation/core/index.js +1 -0
  34. package/lib/citation/core/index.js.map +1 -1
  35. package/lib/citation/core/teachingPaper.d.ts +15 -0
  36. package/lib/citation/core/teachingPaper.d.ts.map +1 -0
  37. package/lib/citation/core/teachingPaper.js +32 -0
  38. package/lib/citation/core/teachingPaper.js.map +1 -0
  39. package/lib/clipboard/ui/CodeBlock.d.ts.map +1 -1
  40. package/lib/clipboard/ui/CodeBlock.js +4 -1
  41. package/lib/clipboard/ui/CodeBlock.js.map +1 -1
  42. package/lib/color/ui/ColorScaleLegend.js +1 -1
  43. package/lib/color/ui/ColorScaleLegend.js.map +1 -1
  44. package/lib/core.d.ts +3 -0
  45. package/lib/core.d.ts.map +1 -1
  46. package/lib/core.js +5 -0
  47. package/lib/core.js.map +1 -1
  48. package/lib/credits/core/credits.d.ts +36 -0
  49. package/lib/credits/core/credits.d.ts.map +1 -1
  50. package/lib/credits/core/credits.js +42 -0
  51. package/lib/credits/core/credits.js.map +1 -1
  52. package/lib/credits/ui/CreditsList.js +1 -1
  53. package/lib/credits/ui/CreditsList.js.map +1 -1
  54. package/lib/delimited/ui/DelimitedTextPanel.js +1 -1
  55. package/lib/delimited/ui/DelimitedTextPanel.js.map +1 -1
  56. package/lib/ecosystem/core/sites.d.ts +1 -1
  57. package/lib/ecosystem/core/sites.d.ts.map +1 -1
  58. package/lib/ecosystem/core/sites.js +10 -0
  59. package/lib/ecosystem/core/sites.js.map +1 -1
  60. package/lib/ecosystem/core/tokens.d.ts.map +1 -1
  61. package/lib/ecosystem/core/tokens.js +1 -0
  62. package/lib/ecosystem/core/tokens.js.map +1 -1
  63. package/lib/ecosystem/ui/EcosystemLinks.js +2 -2
  64. package/lib/ecosystem/ui/EcosystemLinks.js.map +1 -1
  65. package/lib/ecosystem/ui/EcosystemMenu.js +1 -1
  66. package/lib/ecosystem/ui/EcosystemMenu.js.map +1 -1
  67. package/lib/ecosystem/ui/SiteTile.d.ts.map +1 -1
  68. package/lib/ecosystem/ui/SiteTile.js +5 -5
  69. package/lib/ecosystem/ui/SiteTile.js.map +1 -1
  70. package/lib/ecosystem/ui/glyphs.d.ts.map +1 -1
  71. package/lib/ecosystem/ui/glyphs.js +3 -0
  72. package/lib/ecosystem/ui/glyphs.js.map +1 -1
  73. package/lib/orbital/core/index.d.ts +1 -1
  74. package/lib/orbital/core/index.d.ts.map +1 -1
  75. package/lib/orbital/core/index.js +1 -1
  76. package/lib/orbital/core/index.js.map +1 -1
  77. package/lib/orbital/core/realHarmonics.d.ts +5 -2
  78. package/lib/orbital/core/realHarmonics.d.ts.map +1 -1
  79. package/lib/orbital/core/realHarmonics.js.map +1 -1
  80. package/lib/orbital/ui/AtomicOrbitalCanvas.d.ts +18 -0
  81. package/lib/orbital/ui/AtomicOrbitalCanvas.d.ts.map +1 -1
  82. package/lib/orbital/ui/AtomicOrbitalCanvas.js +84 -7
  83. package/lib/orbital/ui/AtomicOrbitalCanvas.js.map +1 -1
  84. package/lib/orbital/ui/AtomicOrbitalViewer.d.ts +13 -0
  85. package/lib/orbital/ui/AtomicOrbitalViewer.d.ts.map +1 -1
  86. package/lib/orbital/ui/AtomicOrbitalViewer.js +1 -1
  87. package/lib/orbital/ui/AtomicOrbitalViewer.js.map +1 -1
  88. package/lib/orbital/ui/camera.d.ts +15 -0
  89. package/lib/orbital/ui/camera.d.ts.map +1 -1
  90. package/lib/orbital/ui/camera.js +68 -0
  91. package/lib/orbital/ui/camera.js.map +1 -1
  92. package/lib/orbital/ui/renderVolume.d.ts.map +1 -1
  93. package/lib/orbital/ui/renderVolume.js +8 -1
  94. package/lib/orbital/ui/renderVolume.js.map +1 -1
  95. package/lib/orbital/ui/serial.d.ts +25 -0
  96. package/lib/orbital/ui/serial.d.ts.map +1 -0
  97. package/lib/orbital/ui/serial.js +25 -0
  98. package/lib/orbital/ui/serial.js.map +1 -0
  99. package/lib/orbital/ui/viewer.d.ts +23 -0
  100. package/lib/orbital/ui/viewer.d.ts.map +1 -1
  101. package/lib/orbital/ui/viewer.js +70 -17
  102. package/lib/orbital/ui/viewer.js.map +1 -1
  103. package/lib/pedagogy/ui/ExerciseProgressHeader.js +1 -1
  104. package/lib/pedagogy/ui/ExerciseProgressHeader.js.map +1 -1
  105. package/lib/pedagogy/ui/GlossaryText.js +2 -2
  106. package/lib/pedagogy/ui/GlossaryText.js.map +1 -1
  107. package/lib/pedagogy/ui/ReferenceSectionBlock.js +5 -2
  108. package/lib/pedagogy/ui/ReferenceSectionBlock.js.map +1 -1
  109. package/lib/pedagogy/ui/SyntaxTooltip.js +2 -2
  110. package/lib/pedagogy/ui/SyntaxTooltip.js.map +1 -1
  111. package/lib/pedagogy/ui/TutorialStepStrip.d.ts +15 -2
  112. package/lib/pedagogy/ui/TutorialStepStrip.d.ts.map +1 -1
  113. package/lib/pedagogy/ui/TutorialStepStrip.js +19 -5
  114. package/lib/pedagogy/ui/TutorialStepStrip.js.map +1 -1
  115. package/lib/pedagogy/ui/index.d.ts +1 -1
  116. package/lib/pedagogy/ui/index.d.ts.map +1 -1
  117. package/lib/pedagogy/ui/index.js.map +1 -1
  118. package/lib/periodic/ui/CategoryLegend.js +1 -1
  119. package/lib/periodic/ui/CategoryLegend.js.map +1 -1
  120. package/lib/periodic/ui/PeriodicTableChrome.js +2 -2
  121. package/lib/periodic/ui/PeriodicTableChrome.js.map +1 -1
  122. package/lib/seo/vite/ogCard.d.ts.map +1 -1
  123. package/lib/seo/vite/ogCard.js +1 -0
  124. package/lib/seo/vite/ogCard.js.map +1 -1
  125. package/lib/share/ui/ShareDialogContent.d.ts.map +1 -1
  126. package/lib/share/ui/ShareDialogContent.js +5 -2
  127. package/lib/share/ui/ShareDialogContent.js.map +1 -1
  128. package/lib/share/ui/SharePartOptions.js +1 -1
  129. package/lib/share/ui/SharePartOptions.js.map +1 -1
  130. package/lib/slides/core/demoLinks.d.ts +43 -0
  131. package/lib/slides/core/demoLinks.d.ts.map +1 -0
  132. package/lib/slides/core/demoLinks.js +104 -0
  133. package/lib/slides/core/demoLinks.js.map +1 -0
  134. package/lib/slides/core/index.d.ts +13 -0
  135. package/lib/slides/core/index.d.ts.map +1 -0
  136. package/lib/slides/core/index.js +7 -0
  137. package/lib/slides/core/index.js.map +1 -0
  138. package/lib/slides/core/sectionHeading.d.ts +19 -0
  139. package/lib/slides/core/sectionHeading.d.ts.map +1 -0
  140. package/lib/slides/core/sectionHeading.js +19 -0
  141. package/lib/slides/core/sectionHeading.js.map +1 -0
  142. package/lib/slides/core/slideNavigation.d.ts +38 -0
  143. package/lib/slides/core/slideNavigation.d.ts.map +1 -0
  144. package/lib/slides/core/slideNavigation.js +65 -0
  145. package/lib/slides/core/slideNavigation.js.map +1 -0
  146. package/lib/slides/core/talk.d.ts +82 -0
  147. package/lib/slides/core/talk.d.ts.map +1 -0
  148. package/lib/slides/core/talk.js +134 -0
  149. package/lib/slides/core/talk.js.map +1 -0
  150. package/lib/slides/core/talkManifest.d.ts +90 -0
  151. package/lib/slides/core/talkManifest.d.ts.map +1 -0
  152. package/lib/slides/core/talkManifest.js +109 -0
  153. package/lib/slides/core/talkManifest.js.map +1 -0
  154. package/lib/slides/core/talkOrigin.d.ts +50 -0
  155. package/lib/slides/core/talkOrigin.d.ts.map +1 -0
  156. package/lib/slides/core/talkOrigin.js +62 -0
  157. package/lib/slides/core/talkOrigin.js.map +1 -0
  158. package/lib/slides/ui/BackToSlides.d.ts +26 -0
  159. package/lib/slides/ui/BackToSlides.d.ts.map +1 -0
  160. package/lib/slides/ui/BackToSlides.js +24 -0
  161. package/lib/slides/ui/BackToSlides.js.map +1 -0
  162. package/lib/slides/ui/DemoLink.d.ts +42 -0
  163. package/lib/slides/ui/DemoLink.d.ts.map +1 -0
  164. package/lib/slides/ui/DemoLink.js +17 -0
  165. package/lib/slides/ui/DemoLink.js.map +1 -0
  166. package/lib/slides/ui/EmbedSlide.d.ts +19 -0
  167. package/lib/slides/ui/EmbedSlide.d.ts.map +1 -0
  168. package/lib/slides/ui/EmbedSlide.js +56 -0
  169. package/lib/slides/ui/EmbedSlide.js.map +1 -0
  170. package/lib/slides/ui/SectionSlide.d.ts +18 -0
  171. package/lib/slides/ui/SectionSlide.d.ts.map +1 -0
  172. package/lib/slides/ui/SectionSlide.js +18 -0
  173. package/lib/slides/ui/SectionSlide.js.map +1 -0
  174. package/lib/slides/ui/Slide.d.ts +52 -0
  175. package/lib/slides/ui/Slide.d.ts.map +1 -0
  176. package/lib/slides/ui/Slide.js +58 -0
  177. package/lib/slides/ui/Slide.js.map +1 -0
  178. package/lib/slides/ui/SlideBody.d.ts +32 -0
  179. package/lib/slides/ui/SlideBody.d.ts.map +1 -0
  180. package/lib/slides/ui/SlideBody.js +27 -0
  181. package/lib/slides/ui/SlideBody.js.map +1 -0
  182. package/lib/slides/ui/Slideshow.d.ts +93 -0
  183. package/lib/slides/ui/Slideshow.d.ts.map +1 -0
  184. package/lib/slides/ui/Slideshow.js +122 -0
  185. package/lib/slides/ui/Slideshow.js.map +1 -0
  186. package/lib/slides/ui/SlideshowBar.d.ts +33 -0
  187. package/lib/slides/ui/SlideshowBar.d.ts.map +1 -0
  188. package/lib/slides/ui/SlideshowBar.js +20 -0
  189. package/lib/slides/ui/SlideshowBar.js.map +1 -0
  190. package/lib/slides/ui/TalkList.d.ts +37 -0
  191. package/lib/slides/ui/TalkList.d.ts.map +1 -0
  192. package/lib/slides/ui/TalkList.js +68 -0
  193. package/lib/slides/ui/TalkList.js.map +1 -0
  194. package/lib/slides/ui/index.d.ts +19 -0
  195. package/lib/slides/ui/index.d.ts.map +1 -0
  196. package/lib/slides/ui/index.js +10 -0
  197. package/lib/slides/ui/index.js.map +1 -0
  198. package/lib/slides/vite/index.d.ts +3 -0
  199. package/lib/slides/vite/index.d.ts.map +1 -0
  200. package/lib/slides/vite/index.js +2 -0
  201. package/lib/slides/vite/index.js.map +1 -0
  202. package/lib/slides/vite/talks.d.ts +50 -0
  203. package/lib/slides/vite/talks.d.ts.map +1 -0
  204. package/lib/slides/vite/talks.js +87 -0
  205. package/lib/slides/vite/talks.js.map +1 -0
  206. package/lib/slides.d.ts +3 -0
  207. package/lib/slides.d.ts.map +1 -0
  208. package/lib/slides.js +3 -0
  209. package/lib/slides.js.map +1 -0
  210. package/lib/structure/ui/StructureEditor.js +2 -2
  211. package/lib/structure/ui/StructureEditor.js.map +1 -1
  212. package/lib/structure/ui/StructurePlaceholder.js +1 -1
  213. package/lib/structure/ui/StructurePlaceholder.js.map +1 -1
  214. package/lib/tokens/core/checkTokens.d.ts +51 -0
  215. package/lib/tokens/core/checkTokens.d.ts.map +1 -0
  216. package/lib/tokens/core/checkTokens.js +188 -0
  217. package/lib/tokens/core/checkTokens.js.map +1 -0
  218. package/lib/tokens/core/colors.d.ts +42 -0
  219. package/lib/tokens/core/colors.d.ts.map +1 -0
  220. package/lib/tokens/core/colors.js +119 -0
  221. package/lib/tokens/core/colors.js.map +1 -0
  222. package/lib/tokens/core/index.d.ts +3 -0
  223. package/lib/tokens/core/index.d.ts.map +1 -0
  224. package/lib/tokens/core/index.js +2 -0
  225. package/lib/tokens/core/index.js.map +1 -0
  226. package/lib/tokens/core/rules.d.ts +23 -0
  227. package/lib/tokens/core/rules.d.ts.map +1 -0
  228. package/lib/tokens/core/rules.js +90 -0
  229. package/lib/tokens/core/rules.js.map +1 -0
  230. package/lib/ui.d.ts +1 -0
  231. package/lib/ui.d.ts.map +1 -1
  232. package/lib/ui.js +1 -0
  233. package/lib/ui.js.map +1 -1
  234. package/lib/vite.d.ts +1 -0
  235. package/lib/vite.d.ts.map +1 -1
  236. package/lib/vite.js +1 -0
  237. package/lib/vite.js.map +1 -1
  238. package/package.json +11 -3
  239. package/src/about/core/about.ts +176 -0
  240. package/src/about/core/index.ts +2 -0
  241. package/src/about/ui/AboutCitations.tsx +79 -0
  242. package/src/about/ui/AboutPage.tsx +177 -0
  243. package/src/about/ui/AboutSection.tsx +56 -0
  244. package/src/about/ui/index.ts +6 -0
  245. package/src/chrome/ui/index.ts +3 -0
  246. package/src/citation/core/index.ts +1 -0
  247. package/src/citation/core/teachingPaper.ts +36 -0
  248. package/src/clipboard/ui/CodeBlock.tsx +4 -1
  249. package/src/color/ui/ColorScaleLegend.tsx +1 -1
  250. package/src/core.ts +5 -0
  251. package/src/credits/core/credits.ts +42 -0
  252. package/src/credits/ui/CreditsList.tsx +1 -1
  253. package/src/delimited/ui/DelimitedTextPanel.tsx +1 -1
  254. package/src/ecosystem/core/sites.ts +12 -0
  255. package/src/ecosystem/core/tokens.ts +1 -0
  256. package/src/ecosystem/ui/EcosystemLinks.tsx +2 -2
  257. package/src/ecosystem/ui/EcosystemMenu.tsx +1 -1
  258. package/src/ecosystem/ui/SiteTile.tsx +7 -5
  259. package/src/ecosystem/ui/glyphs.tsx +40 -0
  260. package/src/orbital/core/index.ts +4 -1
  261. package/src/orbital/core/realHarmonics.ts +5 -2
  262. package/src/orbital/ui/AtomicOrbitalCanvas.tsx +126 -4
  263. package/src/orbital/ui/AtomicOrbitalViewer.tsx +14 -1
  264. package/src/orbital/ui/camera.ts +81 -0
  265. package/src/orbital/ui/renderVolume.ts +8 -1
  266. package/src/orbital/ui/serial.ts +35 -0
  267. package/src/orbital/ui/viewer.ts +72 -13
  268. package/src/pedagogy/ui/ExerciseProgressHeader.tsx +1 -1
  269. package/src/pedagogy/ui/GlossaryText.tsx +2 -2
  270. package/src/pedagogy/ui/ReferenceSectionBlock.tsx +5 -2
  271. package/src/pedagogy/ui/SyntaxTooltip.tsx +2 -2
  272. package/src/pedagogy/ui/TutorialStepStrip.tsx +39 -10
  273. package/src/pedagogy/ui/index.ts +4 -1
  274. package/src/periodic/ui/CategoryLegend.tsx +1 -1
  275. package/src/periodic/ui/PeriodicTableChrome.tsx +2 -2
  276. package/src/seo/vite/ogCard.ts +1 -0
  277. package/src/share/ui/ShareDialogContent.tsx +5 -2
  278. package/src/share/ui/SharePartOptions.tsx +1 -1
  279. package/src/slides/core/demoLinks.ts +113 -0
  280. package/src/slides/core/index.ts +22 -0
  281. package/src/slides/core/sectionHeading.ts +29 -0
  282. package/src/slides/core/slideNavigation.ts +77 -0
  283. package/src/slides/core/talk.ts +203 -0
  284. package/src/slides/core/talkManifest.ts +158 -0
  285. package/src/slides/core/talkOrigin.ts +83 -0
  286. package/src/slides/ui/BackToSlides.tsx +49 -0
  287. package/src/slides/ui/DemoLink.tsx +67 -0
  288. package/src/slides/ui/EmbedSlide.tsx +85 -0
  289. package/src/slides/ui/SectionSlide.tsx +44 -0
  290. package/src/slides/ui/Slide.tsx +143 -0
  291. package/src/slides/ui/SlideBody.tsx +64 -0
  292. package/src/slides/ui/Slideshow.tsx +282 -0
  293. package/src/slides/ui/SlideshowBar.tsx +83 -0
  294. package/src/slides/ui/TalkList.tsx +146 -0
  295. package/src/slides/ui/index.ts +18 -0
  296. package/src/slides/vite/index.ts +2 -0
  297. package/src/slides/vite/talks.ts +126 -0
  298. package/src/slides.ts +2 -0
  299. package/src/structure/ui/StructureEditor.tsx +2 -2
  300. package/src/structure/ui/StructurePlaceholder.tsx +1 -1
  301. package/src/tokens/core/checkTokens.ts +242 -0
  302. package/src/tokens/core/colors.ts +122 -0
  303. package/src/tokens/core/index.ts +6 -0
  304. package/src/tokens/core/rules.ts +103 -0
  305. package/src/ui.ts +1 -0
  306. package/src/vite.ts +1 -0
  307. package/styles/chrome.css +1 -0
  308. package/styles/slides.css +667 -0
@@ -1,15 +1,29 @@
1
1
  import { Button, Tag, Tooltip } from '@blueprintjs/core';
2
2
  import type { CSSProperties, ReactElement, ReactNode } from 'react';
3
3
 
4
- import type { ExerciseLevel, TutorialStep } from '../core/types.ts';
4
+ import type { ExerciseLevel } from '../core/types.ts';
5
5
 
6
6
  import { LEVEL_ORDER } from './exerciseMeta.ts';
7
7
  import type { TutorialLevelColours } from './tutorialLevels.ts';
8
8
  import { TUTORIAL_LEVEL_COLOURS } from './tutorialLevels.ts';
9
9
 
10
+ /**
11
+ * The little a strip needs of a step: what to call it, and which coloured group
12
+ * it belongs to. A tool passes its own steps, whatever else they carry.
13
+ */
14
+ export interface TutorialStripStep {
15
+ /**
16
+ * What keys the button, when the tool gives its steps one.
17
+ * @default undefined — the position in the tour keys it instead
18
+ */
19
+ id?: string;
20
+ title: string;
21
+ level: ExerciseLevel;
22
+ }
23
+
10
24
  export interface TutorialStepStripProps {
11
25
  /** The whole tour, in teaching order. */
12
- steps: readonly TutorialStep[];
26
+ steps: readonly TutorialStripStep[];
13
27
  /**
14
28
  * Which step is open, as its position in `steps`. A position outside the
15
29
  * tour is read as one of its ends, so a hand-edited link still opens.
@@ -110,7 +124,7 @@ export function TutorialStepStrip(props: TutorialStepStripProps): ReactElement {
110
124
  }
111
125
 
112
126
  interface LevelStripProps {
113
- steps: readonly TutorialStep[];
127
+ steps: readonly TutorialStripStep[];
114
128
  positions: readonly number[];
115
129
  colours: TutorialLevelColours;
116
130
  label: string;
@@ -122,11 +136,11 @@ function LevelStrip(props: LevelStripProps): ReactElement {
122
136
  const { steps, positions, colours, label, activeIndex, onSelect } = props;
123
137
  const buttons: ReactNode[] = [];
124
138
  for (const position of positions) {
125
- const step = steps[position] as TutorialStep;
139
+ const step = steps[position] as TutorialStripStep;
126
140
  const active = position === activeIndex;
127
141
  buttons.push(
128
142
  <Tooltip
129
- key={step.id}
143
+ key={step.id ?? position}
130
144
  content={step.title}
131
145
  hoverOpenDelay={HOVER_OPEN_DELAY}
132
146
  placement="bottom"
@@ -155,7 +169,7 @@ function LevelStrip(props: LevelStripProps): ReactElement {
155
169
  }
156
170
 
157
171
  function positionsOfLevel(
158
- steps: readonly TutorialStep[],
172
+ steps: readonly TutorialStripStep[],
159
173
  level: ExerciseLevel,
160
174
  ): number[] {
161
175
  const positions: number[] = [];
@@ -172,6 +186,9 @@ function clampIndex(index: number, total: number): number {
172
186
 
173
187
  /**
174
188
  * The open step takes the darker shade of its own level, plus a border.
189
+ *
190
+ * Every button is the same square whatever it holds, so the numbers of one
191
+ * strip stand above those of the next and the tour reads as a grid.
175
192
  * @param colours - The two shades of the level the step belongs to.
176
193
  * @param active - Whether this is the step on screen.
177
194
  * @returns The style of one numbered button.
@@ -181,20 +198,29 @@ function numberStyle(
181
198
  active: boolean,
182
199
  ): CSSProperties {
183
200
  return {
184
- background: active ? colours.activeBackground : '#ffffff',
201
+ display: 'inline-flex',
202
+ width: STEP_SIZE,
203
+ height: STEP_SIZE,
204
+ alignItems: 'center',
205
+ justifyContent: 'center',
206
+ padding: 0,
185
207
  border: active ? '2px solid #1c2127' : '1px solid rgb(17 20 24 / 20%)',
186
208
  borderRadius: 4,
209
+ background: active ? colours.activeBackground : '#ffffff',
210
+ boxSizing: 'border-box',
187
211
  color: '#1c2127',
188
212
  cursor: 'pointer',
189
213
  font: 'inherit',
190
214
  fontSize: 12,
215
+ fontVariantNumeric: 'tabular-nums',
191
216
  fontWeight: active ? 700 : 500,
192
217
  lineHeight: 1,
193
- minWidth: 26,
194
- padding: active ? '5px 6px' : '6px 7px',
195
218
  };
196
219
  }
197
220
 
221
+ /** Wide enough for two digits, which every tour of ours stays under a hundred of. */
222
+ const STEP_SIZE = 28;
223
+
198
224
  /** Long enough that the pointer can cross the strip without opening a title. */
199
225
  const HOVER_OPEN_DELAY = 150;
200
226
 
@@ -227,4 +253,7 @@ const PAGER_STYLE: CSSProperties = {
227
253
  paddingTop: 2,
228
254
  };
229
255
 
230
- const HINT_STYLE: CSSProperties = { color: '#5b6875', fontSize: 11 };
256
+ const HINT_STYLE: CSSProperties = {
257
+ color: 'var(--text-muted, #5b6875)',
258
+ fontSize: 11,
259
+ };
@@ -31,7 +31,10 @@ export type {
31
31
  export { SyntaxTooltip } from './SyntaxTooltip.tsx';
32
32
  export type { TestCaseListProps } from './TestCaseList.tsx';
33
33
  export { TestCaseList } from './TestCaseList.tsx';
34
- export type { TutorialStepStripProps } from './TutorialStepStrip.tsx';
34
+ export type {
35
+ TutorialStepStripProps,
36
+ TutorialStripStep,
37
+ } from './TutorialStepStrip.tsx';
35
38
  export { TutorialStepStrip } from './TutorialStepStrip.tsx';
36
39
  export type { TutorialLevelColours } from './tutorialLevels.ts';
37
40
  export { TUTORIAL_LEVEL_COLOURS } from './tutorialLevels.ts';
@@ -74,7 +74,7 @@ export function CategoryLegend(props: CategoryLegendProps): ReactElement {
74
74
  }
75
75
 
76
76
  const legendStyle = {
77
- color: 'rgb(95 107 124)',
77
+ color: 'var(--text-muted, rgb(95 107 124))',
78
78
  display: 'flex',
79
79
  flexWrap: 'wrap',
80
80
  fontSize: 11,
@@ -145,7 +145,7 @@ function HeaderCell(props: HeaderCellProps): ReactElement {
145
145
 
146
146
  const headerStyle = {
147
147
  alignItems: 'center',
148
- color: 'rgb(95 107 124)',
148
+ color: 'var(--text-muted, rgb(95 107 124))',
149
149
  display: 'flex',
150
150
  fontSize: 'clamp(0.4rem, 1.9cqw, 0.62rem)',
151
151
  justifyContent: 'center',
@@ -163,7 +163,7 @@ const markerStyle = {
163
163
  alignItems: 'center',
164
164
  border: '1px dashed rgb(182 191 204)',
165
165
  borderRadius: 3,
166
- color: 'rgb(95 107 124)',
166
+ color: 'var(--text-muted, rgb(95 107 124))',
167
167
  display: 'flex',
168
168
  fontSize: 'clamp(0.36rem, 1.7cqw, 0.55rem)',
169
169
  justifyContent: 'center',
@@ -1,3 +1,4 @@
1
+ // tokens-ok: file — the card is an image built outside any page, so it has no tokens to read.
1
2
  /**
2
3
  * The 1200×630 card a link to a site unfurls into, as a page to screenshot.
3
4
  *
@@ -28,7 +28,10 @@ import type { ShareDialogProps, ShareDraft } from './ShareDialog.tsx';
28
28
  import { SharePartOptions } from './SharePartOptions.tsx';
29
29
  import { withPart } from './draft.ts';
30
30
 
31
- const LEAD_STYLE: CSSProperties = { marginTop: 0, color: '#5b6875' };
31
+ const LEAD_STYLE: CSSProperties = {
32
+ marginTop: 0,
33
+ color: 'var(--text-muted, #5b6875)',
34
+ };
32
35
  const SECTION_STYLE: CSSProperties = { marginBottom: 18 };
33
36
  const ACTIONS_STYLE: CSSProperties = {
34
37
  display: 'flex',
@@ -39,7 +42,7 @@ const ACTIONS_STYLE: CSSProperties = {
39
42
  const HINT_STYLE: CSSProperties = {
40
43
  display: 'block',
41
44
  marginLeft: 26,
42
- color: '#5b6875',
45
+ color: 'var(--text-muted, #5b6875)',
43
46
  fontSize: 12,
44
47
  };
45
48
 
@@ -8,7 +8,7 @@ const OPTION_STYLE: CSSProperties = { marginBottom: 8 };
8
8
  const DESCRIPTION_STYLE: CSSProperties = {
9
9
  display: 'block',
10
10
  marginLeft: 26,
11
- color: '#5b6875',
11
+ color: 'var(--text-muted, #5b6875)',
12
12
  fontSize: 12,
13
13
  };
14
14
 
@@ -0,0 +1,113 @@
1
+ /**
2
+ * The links a slide hands over to the live tool.
3
+ *
4
+ * A demo link is a paragraph that is exactly one Markdown link to an in-app
5
+ * route. It is parsed by hand rather than by a Markdown library because the
6
+ * labels are chemistry — `[M+H]⁺` opens a bracket the label closes itself, and
7
+ * a formula such as `H(OCH2CH2)10` puts parentheses inside the URL — and both
8
+ * defeat a naive `\[(.*)\]\((.*)\)` pattern.
9
+ */
10
+
11
+ /** One link a slide offers as a live demo. */
12
+ export interface DemoLinkSpec {
13
+ /** The link text, which may itself contain balanced brackets. */
14
+ label: string;
15
+ /** Where it goes; a demo link always targets an in-app route. */
16
+ href: string;
17
+ }
18
+
19
+ /**
20
+ * Read a paragraph that is exactly one Markdown link `[label](url)` and nothing
21
+ * else, counting brackets so a label may hold its own pair, and accepting the
22
+ * angle-bracketed URL form `[label](<url>)` for a URL holding parentheses.
23
+ * @param text - The paragraph text.
24
+ * @returns The link, or `null` when the paragraph is anything else.
25
+ */
26
+ export function parseSingleLink(text: string): DemoLinkSpec | null {
27
+ const source = text.trim();
28
+ if (!source.startsWith('[')) return null;
29
+
30
+ let depth = 0;
31
+ let labelEnd = -1;
32
+ for (let index = 0; index < source.length; index++) {
33
+ const char = source[index];
34
+ if (char === '[') {
35
+ depth++;
36
+ } else if (char === ']') {
37
+ depth--;
38
+ if (depth === 0) {
39
+ labelEnd = index;
40
+ break;
41
+ }
42
+ }
43
+ }
44
+ if (labelEnd === -1 || source[labelEnd + 1] !== '(') return null;
45
+
46
+ const label = source.slice(1, labelEnd);
47
+ let rest = source.slice(labelEnd + 2);
48
+ let href: string;
49
+
50
+ if (rest.startsWith('<')) {
51
+ const close = rest.indexOf('>');
52
+ if (close === -1 || rest[close + 1] !== ')') return null;
53
+ href = rest.slice(1, close);
54
+ rest = rest.slice(close + 2);
55
+ } else {
56
+ let parenDepth = 1;
57
+ let urlEnd = -1;
58
+ for (let index = 0; index < rest.length; index++) {
59
+ const char = rest[index];
60
+ if (char === '(') {
61
+ parenDepth++;
62
+ } else if (char === ')') {
63
+ parenDepth--;
64
+ if (parenDepth === 0) {
65
+ urlEnd = index;
66
+ break;
67
+ }
68
+ }
69
+ }
70
+ if (urlEnd === -1) return null;
71
+ href = rest.slice(0, urlEnd);
72
+ rest = rest.slice(urlEnd + 1);
73
+ }
74
+
75
+ if (rest.trim() !== '') return null;
76
+ return { label, href };
77
+ }
78
+
79
+ /**
80
+ * Split a slide body into its Markdown and the run of demo links that closes
81
+ * it. External links and ordinary paragraphs stay in the body, so a slide that
82
+ * merely cites a URL keeps it where the author wrote it.
83
+ * @param body - The slide Markdown body.
84
+ * @returns The body without its trailing demo links, and those links in order.
85
+ */
86
+ export function splitDemoLinks(body: string): {
87
+ body: string;
88
+ demos: DemoLinkSpec[];
89
+ } {
90
+ const blocks = body.split(/\n{2,}/);
91
+ let firstDemo = blocks.length;
92
+ const demos: DemoLinkSpec[] = [];
93
+ for (let index = blocks.length - 1; index >= 0; index--) {
94
+ const link = parseSingleLink(blocks[index] ?? '');
95
+ if (!link?.href.startsWith('/')) break;
96
+ demos.unshift(link);
97
+ firstDemo = index;
98
+ }
99
+ return { body: blocks.slice(0, firstDemo).join('\n\n').trim(), demos };
100
+ }
101
+
102
+ /**
103
+ * The displayed form of a demo link label: the `Demo:` prefix an author writes
104
+ * to mark the paragraph is dropped, and the first character is uppercased.
105
+ * @param label - The raw link label.
106
+ * @returns What the button shows.
107
+ */
108
+ export function demoLabel(label: string): string {
109
+ const text = label.replace(DEMO_PREFIX, '');
110
+ return text.charAt(0).toUpperCase() + text.slice(1);
111
+ }
112
+
113
+ const DEMO_PREFIX = /^Demo:\s*/i;
@@ -0,0 +1,22 @@
1
+ export type { DemoLinkSpec } from './demoLinks.ts';
2
+ export { demoLabel, parseSingleLink, splitDemoLinks } from './demoLinks.ts';
3
+ export type { SectionHeading } from './sectionHeading.ts';
4
+ export { parseSectionHeading } from './sectionHeading.ts';
5
+ export type { SlideAction } from './slideNavigation.ts';
6
+ export { clampSlideIndex, slideActionForKey } from './slideNavigation.ts';
7
+ export type { Slide, Talk, TalkMeta } from './talk.ts';
8
+ export { parseTalk } from './talk.ts';
9
+ export type { TalkManifest, TalkSummary } from './talkManifest.ts';
10
+ export {
11
+ buildTalkManifest,
12
+ parseTalkManifest,
13
+ talkSourceUrl,
14
+ talkSummary,
15
+ } from './talkManifest.ts';
16
+ export type { TalkOrigin } from './talkOrigin.ts';
17
+ export {
18
+ TALK_ORIGIN_PARAM,
19
+ formatTalkOrigin,
20
+ parseTalkOrigin,
21
+ withTalkOrigin,
22
+ } from './talkOrigin.ts';
@@ -0,0 +1,29 @@
1
+ /** The parts of a section divider's heading. */
2
+ export interface SectionHeading {
3
+ /** The section number, as written; empty when the heading carries none. */
4
+ number: string;
5
+ /** The section title, without its number; empty when the heading carries none. */
6
+ title: string;
7
+ /** Whatever the body holds besides that heading. */
8
+ rest: string;
9
+ }
10
+
11
+ /**
12
+ * Split a section-divider body into its leading number, its title and the rest
13
+ * of the Markdown, so a template can set the number apart from the title.
14
+ * `·`, `.`, `:`, `—` and `-` all separate the two.
15
+ * @param body - Markdown body, normally starting with `# N · Title`.
16
+ * @returns The parsed heading; a body with no numbered heading keeps all of
17
+ * itself in `rest`, leaving `number` and `title` empty.
18
+ */
19
+ export function parseSectionHeading(body: string): SectionHeading {
20
+ const match = HEAD_RE.exec(body);
21
+ const number = match?.groups?.number;
22
+ const title = match?.groups?.title;
23
+ if (match === null || number === undefined || title === undefined) {
24
+ return { number: '', title: '', rest: body.trim() };
25
+ }
26
+ return { number, title, rest: body.replace(match[0], '').trim() };
27
+ }
28
+
29
+ const HEAD_RE = /^#\s+(?<number>\d+)\s*[.:·—-]\s*(?<title>.+?)\s*$/m;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * What a key does during a presentation, decided without a DOM.
3
+ *
4
+ * The current slide lives in the address, so it arrives as whatever a link
5
+ * carried — a missing number, a negative one, one past the end of a talk that
6
+ * has since been shortened. Every move is therefore clamped here rather than
7
+ * trusted, and the player only ever renders a slide that exists.
8
+ */
9
+
10
+ /**
11
+ * The slide a requested index resolves to.
12
+ * @param index - The index asked for, from a link or a keystroke.
13
+ * @param total - How many slides the talk holds.
14
+ * @returns A zero-based index that exists, or `0` for a talk with no slides.
15
+ */
16
+ export function clampSlideIndex(index: number, total: number): number {
17
+ if (!Number.isFinite(total) || total < 1) return 0;
18
+ if (Number.isNaN(index) || index < 0) return 0;
19
+ const last = total - 1;
20
+ return index > last ? last : Math.trunc(index);
21
+ }
22
+
23
+ /** What a key press asks the player to do. */
24
+ export type SlideAction =
25
+ | { kind: 'go'; index: number }
26
+ | { kind: 'blank'; color: 'black' | 'white' }
27
+ | { kind: 'fullscreen' }
28
+ | { kind: 'notes' }
29
+ | null;
30
+
31
+ /**
32
+ * The action a key press means, with every move already clamped: stepping past
33
+ * the last slide, or back from the first, stays on the slide it is on rather
34
+ * than leaving the talk.
35
+ * @param key - The `key` of the keyboard event.
36
+ * @param index - Which slide is showing.
37
+ * @param total - How many slides the talk holds.
38
+ * @returns The action, or `null` when the key means nothing here.
39
+ */
40
+ export function slideActionForKey(
41
+ key: string,
42
+ index: number,
43
+ total: number,
44
+ ): SlideAction {
45
+ switch (key) {
46
+ case 'ArrowRight':
47
+ case 'ArrowDown':
48
+ case 'PageDown':
49
+ case ' ':
50
+ return { kind: 'go', index: clampSlideIndex(index + 1, total) };
51
+ case 'ArrowLeft':
52
+ case 'ArrowUp':
53
+ case 'PageUp':
54
+ return { kind: 'go', index: clampSlideIndex(index - 1, total) };
55
+ case 'Home':
56
+ return { kind: 'go', index: 0 };
57
+ case 'End':
58
+ return { kind: 'go', index: clampSlideIndex(total - 1, total) };
59
+ default:
60
+ return letterAction(key);
61
+ }
62
+ }
63
+
64
+ function letterAction(key: string): SlideAction {
65
+ switch (key.toLowerCase()) {
66
+ case 'f':
67
+ return { kind: 'fullscreen' };
68
+ case 'b':
69
+ return { kind: 'blank', color: 'black' };
70
+ case 'w':
71
+ return { kind: 'blank', color: 'white' };
72
+ case 'n':
73
+ return { kind: 'notes' };
74
+ default:
75
+ return null;
76
+ }
77
+ }
@@ -0,0 +1,203 @@
1
+ /**
2
+ * The talk format: one Markdown file is one talk, and this reads it.
3
+ *
4
+ * A talk is authored as prose rather than as data — an optional front-matter
5
+ * block, then slides separated by a `---` line. Which template draws a slide is
6
+ * a comment the author writes, and the name is deliberately not a closed list:
7
+ * every site registers the layouts it knows how to draw.
8
+ */
9
+
10
+ /** Talk-level metadata, read from the front-matter block. */
11
+ export interface TalkMeta {
12
+ /** Talk title, shown on the title slide and in a talk index. */
13
+ title: string;
14
+ /**
15
+ * A second line under the title.
16
+ * @default undefined
17
+ */
18
+ subtitle?: string;
19
+ /**
20
+ * Conference or event the talk was given at.
21
+ * @default undefined
22
+ */
23
+ event?: string;
24
+ /**
25
+ * When it was given, as an ISO date (`YYYY-MM-DD`).
26
+ * @default undefined
27
+ */
28
+ date?: string;
29
+ /**
30
+ * City or venue.
31
+ * @default undefined
32
+ */
33
+ location?: string;
34
+ /**
35
+ * Who presented it.
36
+ * @default undefined
37
+ */
38
+ author?: string;
39
+ /**
40
+ * Logo image URLs shown on the title slide, written comma-separated in the
41
+ * front-matter.
42
+ * @default undefined
43
+ */
44
+ logos?: string[];
45
+ /**
46
+ * One sentence about the talk, for a listing or a page description.
47
+ * @default undefined
48
+ */
49
+ description?: string;
50
+ }
51
+
52
+ /** One slide of a talk. */
53
+ export interface Slide {
54
+ /**
55
+ * Which template draws the body. A site maps these names to its own
56
+ * components, so any lowercase identifier is accepted here.
57
+ */
58
+ layout: string;
59
+ /** The Markdown body, with the layout marker and the notes comment removed. */
60
+ body: string;
61
+ /**
62
+ * What the presenter says over this slide, never drawn on it.
63
+ * @default undefined
64
+ */
65
+ notes?: string;
66
+ }
67
+
68
+ /** A whole talk: its metadata and its slides, in order. */
69
+ export interface Talk {
70
+ /** What the front-matter said about the talk. */
71
+ meta: TalkMeta;
72
+ /** The slides, in the order they are presented. */
73
+ slides: Slide[];
74
+ }
75
+
76
+ /**
77
+ * Parse a talk written in Markdown into its metadata and its slides.
78
+ *
79
+ * The source is read defensively: CRLF endings, a missing front-matter block,
80
+ * an empty file and a slide holding nothing but whitespace all resolve to
81
+ * something a player can render rather than to an error.
82
+ * @param source - The raw Markdown talk source.
83
+ * @returns The parsed talk; an empty source yields an untitled talk with no slides.
84
+ */
85
+ export function parseTalk(source: string): Talk {
86
+ const normalized = source.replaceAll(/\r\n?/g, '\n');
87
+ const { meta, body } = extractFrontMatter(normalized);
88
+ const slides: Slide[] = [];
89
+ for (const chunk of splitSlides(body)) {
90
+ slides.push(toSlide(chunk));
91
+ }
92
+ return { meta, slides };
93
+ }
94
+
95
+ function extractFrontMatter(source: string): { meta: TalkMeta; body: string } {
96
+ const lines = source.split('\n');
97
+ let index = 0;
98
+ while (index < lines.length && lines[index]?.trim() === '') index++;
99
+ if (lines[index]?.trim() !== '---') {
100
+ return { meta: { title: '' }, body: source };
101
+ }
102
+ index++;
103
+ const metaLines: string[] = [];
104
+ while (index < lines.length && lines[index]?.trim() !== '---') {
105
+ metaLines.push(lines[index] ?? '');
106
+ index++;
107
+ }
108
+ index++;
109
+ return { meta: parseMeta(metaLines), body: lines.slice(index).join('\n') };
110
+ }
111
+
112
+ function parseMeta(metaLines: readonly string[]): TalkMeta {
113
+ const meta: TalkMeta = { title: '' };
114
+ for (const line of metaLines) {
115
+ const match = META_RE.exec(line);
116
+ const key = match?.groups?.key?.toLowerCase();
117
+ const value = match?.groups?.value?.trim();
118
+ if (key === undefined || value === undefined) continue;
119
+ if (key === 'logos') {
120
+ meta.logos = splitLogos(value);
121
+ } else if (TEXT_KEYS.has(key)) {
122
+ meta[key as TextMetaKey] = value;
123
+ }
124
+ }
125
+ return meta;
126
+ }
127
+
128
+ function splitLogos(value: string): string[] {
129
+ const logos: string[] = [];
130
+ for (const logo of value.split(',')) {
131
+ const trimmed = logo.trim();
132
+ if (trimmed !== '') logos.push(trimmed);
133
+ }
134
+ return logos;
135
+ }
136
+
137
+ function splitSlides(body: string): string[] {
138
+ const chunks: string[][] = [[]];
139
+ for (const line of body.split('\n')) {
140
+ if (SEPARATOR_RE.test(line)) {
141
+ chunks.push([]);
142
+ } else {
143
+ chunks.at(-1)?.push(line);
144
+ }
145
+ }
146
+ const slides: string[] = [];
147
+ for (const chunk of chunks) {
148
+ const text = chunk.join('\n').trim();
149
+ if (text !== '') slides.push(text);
150
+ }
151
+ return slides;
152
+ }
153
+
154
+ function toSlide(chunk: string): Slide {
155
+ const layoutMatch = LAYOUT_RE.exec(chunk);
156
+ const { body, notes } = extractNotes(chunk.replace(LAYOUT_RE, ''));
157
+ const layout =
158
+ layoutMatch?.groups?.layout?.toLowerCase() ?? detectLayout(body);
159
+ const slide: Slide = { layout, body };
160
+ if (notes !== '') slide.notes = notes;
161
+ return slide;
162
+ }
163
+
164
+ function extractNotes(chunk: string): { body: string; notes: string } {
165
+ const notes: string[] = [];
166
+ for (const match of chunk.matchAll(NOTES_RE)) {
167
+ const text = match.groups?.notes?.trim();
168
+ if (text !== undefined && text !== '') notes.push(text);
169
+ }
170
+ return {
171
+ body: chunk.replaceAll(NOTES_RE, '').trim(),
172
+ notes: notes.join('\n\n'),
173
+ };
174
+ }
175
+
176
+ function detectLayout(body: string): string {
177
+ let headings = 0;
178
+ for (const line of body.split('\n')) {
179
+ const trimmed = line.trim();
180
+ if (trimmed === '') continue;
181
+ if (!trimmed.startsWith('#')) return 'content';
182
+ headings++;
183
+ }
184
+ return headings > 0 ? 'title' : 'content';
185
+ }
186
+
187
+ /** Which keys of {@link TalkMeta} a front-matter line writes as plain text. */
188
+ type TextMetaKey = Exclude<keyof TalkMeta, 'logos'>;
189
+
190
+ const TEXT_KEYS = new Set<string>([
191
+ 'title',
192
+ 'subtitle',
193
+ 'event',
194
+ 'date',
195
+ 'location',
196
+ 'author',
197
+ 'description',
198
+ ]);
199
+
200
+ const LAYOUT_RE = /<!--\s*layout:\s*(?<layout>[a-z][\da-z-]*)\s*-->/i;
201
+ const NOTES_RE = /<!--\s*notes:\s*(?<notes>.*?)-->/gis;
202
+ const SEPARATOR_RE = /^---[\t ]*$/;
203
+ const META_RE = /^(?<key>[a-z]+)\s*:\s*(?<value>.+)$/i;