mdeck 2.3.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 (268) hide show
  1. package/CHANGELOG.md +259 -0
  2. package/LICENSE +21 -0
  3. package/README.md +156 -0
  4. package/assets/base.css +196 -0
  5. package/assets/extensions/layouts/chapter/extension.toml +26 -0
  6. package/assets/extensions/layouts/chapter/layout.jsx +21 -0
  7. package/assets/extensions/layouts/chapter/starter.md +8 -0
  8. package/assets/extensions/layouts/focus/extension.toml +8 -0
  9. package/assets/extensions/layouts/focus/layout.jsx +10 -0
  10. package/assets/extensions/layouts/focus/starter.md +4 -0
  11. package/assets/extensions/layouts/full-bleed-image/extension.toml +31 -0
  12. package/assets/extensions/layouts/full-bleed-image/layout.jsx +19 -0
  13. package/assets/extensions/layouts/full-bleed-image/starter.md +6 -0
  14. package/assets/extensions/layouts/generic/extension.toml +8 -0
  15. package/assets/extensions/layouts/generic/layout.jsx +8 -0
  16. package/assets/extensions/layouts/generic/starter.md +4 -0
  17. package/assets/extensions/layouts/image-text/extension.toml +25 -0
  18. package/assets/extensions/layouts/image-text/layout.jsx +17 -0
  19. package/assets/extensions/layouts/image-text/starter.md +6 -0
  20. package/assets/extensions/layouts/split/extension.toml +25 -0
  21. package/assets/extensions/layouts/split/layout.jsx +19 -0
  22. package/assets/extensions/layouts/split/starter.md +14 -0
  23. package/assets/extensions/layouts/title/extension.toml +26 -0
  24. package/assets/extensions/layouts/title/layout.jsx +14 -0
  25. package/assets/extensions/layouts/title/starter.md +5 -0
  26. package/assets/extensions/palettes/brand/extension.toml +29 -0
  27. package/assets/extensions/palettes/cobalt/extension.toml +27 -0
  28. package/assets/extensions/palettes/ember/extension.toml +27 -0
  29. package/assets/extensions/palettes/forest/extension.toml +27 -0
  30. package/assets/extensions/palettes/graphite/extension.toml +27 -0
  31. package/assets/extensions/palettes/lagoon/extension.toml +27 -0
  32. package/assets/extensions/palettes/neon/extension.toml +27 -0
  33. package/assets/extensions/palettes/nordic/extension.toml +27 -0
  34. package/assets/extensions/palettes/pastel/extension.toml +30 -0
  35. package/assets/extensions/palettes/phosphor/extension.toml +27 -0
  36. package/assets/extensions/palettes/swiss/extension.toml +27 -0
  37. package/assets/extensions/palettes/terra/extension.toml +27 -0
  38. package/assets/extensions/themes/academic/extension.toml +53 -0
  39. package/assets/extensions/themes/academic/styles.css +631 -0
  40. package/assets/extensions/themes/aurora/extension.toml +37 -0
  41. package/assets/extensions/themes/aurora/styles.css +644 -0
  42. package/assets/extensions/themes/duet/extension.toml +46 -0
  43. package/assets/extensions/themes/duet/styles.css +573 -0
  44. package/assets/extensions/themes/editorial/extension.toml +46 -0
  45. package/assets/extensions/themes/editorial/styles.css +574 -0
  46. package/assets/extensions/themes/fhnw/extension.toml +61 -0
  47. package/assets/extensions/themes/fhnw/styles.css +604 -0
  48. package/assets/extensions/themes/neue/extension.toml +46 -0
  49. package/assets/extensions/themes/neue/styles.css +489 -0
  50. package/assets/extensions/themes/sketch/extension.toml +48 -0
  51. package/assets/extensions/themes/sketch/styles.css +633 -0
  52. package/assets/extensions/themes/terminal/extension.toml +38 -0
  53. package/assets/extensions/themes/terminal/styles.css +550 -0
  54. package/assets/logo/avatar.png +0 -0
  55. package/assets/logo/logo-dark.svg +10 -0
  56. package/assets/logo/logo-light.svg +10 -0
  57. package/assets/logo/mark.svg +15 -0
  58. package/bin/mdeck.js +2 -0
  59. package/docs/README.md +8 -0
  60. package/docs/reference/authoring.md +795 -0
  61. package/docs/reference/claude-skill.md +96 -0
  62. package/docs/reference/extensions.md +250 -0
  63. package/docs/reference/layouts.md +153 -0
  64. package/docs/reference/palettes.md +106 -0
  65. package/docs/reference/structured-slides.md +120 -0
  66. package/docs/reference/themes.md +133 -0
  67. package/docs/site/assets/favicon.svg +15 -0
  68. package/docs/site/assets/github-logo.svg +1 -0
  69. package/docs/site/assets/site.css +162 -0
  70. package/docs/site/assets/site.js +123 -0
  71. package/docs/site/build.js +148 -0
  72. package/docs/site/content/appearance.md +203 -0
  73. package/docs/site/content/audience.md +178 -0
  74. package/docs/site/content/commands.md +153 -0
  75. package/docs/site/content/components.md +134 -0
  76. package/docs/site/content/drawing.md +72 -0
  77. package/docs/site/content/editing.md +59 -0
  78. package/docs/site/content/getting-started.md +103 -0
  79. package/docs/site/content/layouts.md +126 -0
  80. package/docs/site/content/media.md +107 -0
  81. package/docs/site/content/more-content.md +130 -0
  82. package/docs/site/content/presenting.md +93 -0
  83. package/docs/site/content/reusable-layouts.md +74 -0
  84. package/docs/site/content/sharing.md +88 -0
  85. package/docs/site/content/theme-authoring.md +144 -0
  86. package/docs/site/content/theme-repository.md +76 -0
  87. package/docs/site/content/troubleshooting.md +92 -0
  88. package/docs/site/content/views.md +56 -0
  89. package/docs/site/content/welcome.md +71 -0
  90. package/docs/site/content/writing.md +121 -0
  91. package/docs/site/examples/first-talk.md +88 -0
  92. package/docs/site/examples/notebook.svg +58 -0
  93. package/docs/site/images/themes/academic-dark.webp +0 -0
  94. package/docs/site/images/themes/academic-light.webp +0 -0
  95. package/docs/site/images/themes/academic.webp +0 -0
  96. package/docs/site/images/themes/aurora-dark.webp +0 -0
  97. package/docs/site/images/themes/aurora-light.webp +0 -0
  98. package/docs/site/images/themes/aurora.webp +0 -0
  99. package/docs/site/images/themes/duet-dark.webp +0 -0
  100. package/docs/site/images/themes/duet-light.webp +0 -0
  101. package/docs/site/images/themes/duet.webp +0 -0
  102. package/docs/site/images/themes/editorial-dark.webp +0 -0
  103. package/docs/site/images/themes/editorial-light.webp +0 -0
  104. package/docs/site/images/themes/editorial.webp +0 -0
  105. package/docs/site/images/themes/fhnw.webp +0 -0
  106. package/docs/site/images/themes/neue-dark.webp +0 -0
  107. package/docs/site/images/themes/neue-light.webp +0 -0
  108. package/docs/site/images/themes/neue.webp +0 -0
  109. package/docs/site/images/themes/sketch-dark.webp +0 -0
  110. package/docs/site/images/themes/sketch-light.webp +0 -0
  111. package/docs/site/images/themes/sketch.webp +0 -0
  112. package/docs/site/images/themes/terminal-dark.webp +0 -0
  113. package/docs/site/images/themes/terminal-light.webp +0 -0
  114. package/docs/site/images/themes/terminal.webp +0 -0
  115. package/docs/site/package.json +9 -0
  116. package/docs/site/pages.js +27 -0
  117. package/docs/site/server.js +58 -0
  118. package/examples/custom-layouts/extensions/comparison/extension.toml +35 -0
  119. package/examples/custom-layouts/extensions/comparison/layout.jsx +15 -0
  120. package/examples/custom-layouts/extensions/comparison/starter.md +17 -0
  121. package/examples/custom-layouts/extensions/comparison/styles.css +4 -0
  122. package/examples/custom-layouts/slides.md +60 -0
  123. package/examples/poll/slides.md +127 -0
  124. package/examples/python/.mdeck-backups/slides.drawings-2026-10-06T08-36-47Z.json +19 -0
  125. package/examples/python/.mdeck-backups/slides.drawings-2026-10-06T11-27-30Z.json +6 -0
  126. package/examples/python/slides.drawings.json +6 -0
  127. package/examples/python/slides.md +493 -0
  128. package/examples/showcase/.mdeck-backups/slides.drawings-2026-10-06T08-08-30Z.json +10 -0
  129. package/examples/showcase/.mdeck-backups/slides.drawings-2026-10-06T11-41-35Z.json +13 -0
  130. package/examples/showcase/img/image.jpg +0 -0
  131. package/examples/showcase/img/mdeck.svg +66 -0
  132. package/examples/showcase/slides.drawings.json +11 -0
  133. package/examples/showcase/slides.md +427 -0
  134. package/package.json +75 -0
  135. package/skills/write-slides/SKILL.md +144 -0
  136. package/src/build/check.js +49 -0
  137. package/src/build/chrome.js +130 -0
  138. package/src/build/components.js +44 -0
  139. package/src/build/config.js +61 -0
  140. package/src/build/drawings.js +14 -0
  141. package/src/build/editorPlugin.js +272 -0
  142. package/src/build/editorServer.js +36 -0
  143. package/src/build/fonts.js +84 -0
  144. package/src/build/homePlugin.js +208 -0
  145. package/src/build/inkPlugin.js +192 -0
  146. package/src/build/ownWrites.js +18 -0
  147. package/src/build/pairing.js +81 -0
  148. package/src/build/pdf.js +34 -0
  149. package/src/build/relayAccess.js +85 -0
  150. package/src/build/slidesPlugin.js +371 -0
  151. package/src/build/themeCheck.js +151 -0
  152. package/src/build/tunnelClient.js +184 -0
  153. package/src/cli/args.js +76 -0
  154. package/src/cli/index.js +758 -0
  155. package/src/cli/migrate.js +123 -0
  156. package/src/cli/skill.js +40 -0
  157. package/src/cli/themes.js +140 -0
  158. package/src/components/CodeBlock.jsx +168 -0
  159. package/src/components/ConnectedViews.jsx +25 -0
  160. package/src/components/Icon.jsx +8 -0
  161. package/src/components/Poll.jsx +67 -0
  162. package/src/components/QrCode.jsx +29 -0
  163. package/src/components/QrCodeTag.jsx +27 -0
  164. package/src/components/Question.jsx +28 -0
  165. package/src/components/Scale.jsx +47 -0
  166. package/src/components/VideoPlayer.jsx +108 -0
  167. package/src/components/WordCloud.jsx +106 -0
  168. package/src/components/activity.jsx +35 -0
  169. package/src/components/callout.css +78 -0
  170. package/src/components/code-block.css +123 -0
  171. package/src/components/columns.css +11 -0
  172. package/src/components/poll.css +81 -0
  173. package/src/components/video-player.css +14 -0
  174. package/src/core/devPath.js +4 -0
  175. package/src/core/editDeck.js +230 -0
  176. package/src/core/icons.js +69 -0
  177. package/src/core/ink.js +221 -0
  178. package/src/core/labels.js +144 -0
  179. package/src/core/outline.js +33 -0
  180. package/src/core/parseSlides.js +152 -0
  181. package/src/core/source.js +137 -0
  182. package/src/core/urls.js +34 -0
  183. package/src/core/validateDeck.js +139 -0
  184. package/src/editor/api.js +67 -0
  185. package/src/editor/components/App.jsx +75 -0
  186. package/src/editor/components/ConflictBanner.jsx +10 -0
  187. package/src/editor/components/DeckSettings.jsx +108 -0
  188. package/src/editor/components/DesignApp.jsx +252 -0
  189. package/src/editor/components/Diagnostics.jsx +10 -0
  190. package/src/editor/components/ExtensionEditor.jsx +66 -0
  191. package/src/editor/components/ExtensionForms.jsx +134 -0
  192. package/src/editor/components/ExtensionsPanel.jsx +114 -0
  193. package/src/editor/components/Field.jsx +24 -0
  194. package/src/editor/components/Inspector.jsx +36 -0
  195. package/src/editor/components/LayoutPicker.jsx +23 -0
  196. package/src/editor/components/LayoutSelect.jsx +25 -0
  197. package/src/editor/components/LookThumbnail.jsx +8 -0
  198. package/src/editor/components/MetaFields.jsx +24 -0
  199. package/src/editor/components/Outline.jsx +28 -0
  200. package/src/editor/components/PalettePicker.jsx +32 -0
  201. package/src/editor/components/Preview.jsx +63 -0
  202. package/src/editor/components/PropsForm.jsx +46 -0
  203. package/src/editor/components/RawSlideEditor.jsx +9 -0
  204. package/src/editor/components/RegionEditor.jsx +15 -0
  205. package/src/editor/components/Resizer.jsx +18 -0
  206. package/src/editor/designLink.js +19 -0
  207. package/src/editor/editor.css +139 -0
  208. package/src/editor/extensions.js +86 -0
  209. package/src/editor/keyboard.js +21 -0
  210. package/src/editor/model.js +38 -0
  211. package/src/editor/sampleDeck.js +165 -0
  212. package/src/editor/saveQueue.js +47 -0
  213. package/src/editor/schema.js +36 -0
  214. package/src/editor/state.js +92 -0
  215. package/src/editor/useDeckDocument.js +67 -0
  216. package/src/extensions/appearance.js +79 -0
  217. package/src/extensions/contrast.js +57 -0
  218. package/src/extensions/discover.js +126 -0
  219. package/src/extensions/manifest.js +209 -0
  220. package/src/extensions/packs.js +268 -0
  221. package/src/extensions/tokens.js +25 -0
  222. package/src/home/Home.jsx +362 -0
  223. package/src/home/home.css +148 -0
  224. package/src/layouts/layoutApi.jsx +205 -0
  225. package/src/layouts/layoutProps.js +30 -0
  226. package/src/layouts/renderSlide.jsx +17 -0
  227. package/src/live/answer/answer.css +34 -0
  228. package/src/live/answer/answer.html +14 -0
  229. package/src/live/answer/answer.js +130 -0
  230. package/src/live/client.js +290 -0
  231. package/src/live/code.js +19 -0
  232. package/src/live/follow.js +17 -0
  233. package/src/live/pairing.js +34 -0
  234. package/src/live/presence.js +33 -0
  235. package/src/live/roomTag.js +38 -0
  236. package/src/live/rooms.js +249 -0
  237. package/src/live/server.js +82 -0
  238. package/src/live/tunnel.js +180 -0
  239. package/src/paths.js +9 -0
  240. package/src/runtime/ReaderView.jsx +193 -0
  241. package/src/runtime/SlideErrorBoundary.jsx +28 -0
  242. package/src/runtime/chrome.jsx +55 -0
  243. package/src/runtime/deck-stage.js +1533 -0
  244. package/src/runtime/design.html +12 -0
  245. package/src/runtime/design.jsx +5 -0
  246. package/src/runtime/editor.html +12 -0
  247. package/src/runtime/editor.jsx +5 -0
  248. package/src/runtime/editorBridge.js +58 -0
  249. package/src/runtime/fit.js +149 -0
  250. package/src/runtime/home.html +13 -0
  251. package/src/runtime/home.jsx +5 -0
  252. package/src/runtime/index.html +15 -0
  253. package/src/runtime/ink/InkLayer.jsx +17 -0
  254. package/src/runtime/ink/Toolbar.jsx +145 -0
  255. package/src/runtime/ink/attach.js +69 -0
  256. package/src/runtime/ink/bus.js +126 -0
  257. package/src/runtime/ink/controller.js +132 -0
  258. package/src/runtime/ink/room.js +101 -0
  259. package/src/runtime/ink/select.js +124 -0
  260. package/src/runtime/ink/server.js +75 -0
  261. package/src/runtime/ink/store.js +55 -0
  262. package/src/runtime/ink/toolbar.css +79 -0
  263. package/src/runtime/main.jsx +863 -0
  264. package/src/runtime/markedSetup.js +147 -0
  265. package/src/runtime/reader.css +59 -0
  266. package/src/runtime/registry.jsx +26 -0
  267. package/src/runtime/syncOrder.js +32 -0
  268. package/src/runtime/themeLoader.js +68 -0
@@ -0,0 +1,147 @@
1
+ import { marked } from 'marked'
2
+ import markedKatex from 'marked-katex-extension'
3
+ import 'katex/dist/katex.min.css'
4
+ import '../components/callout.css'
5
+ import '../components/columns.css'
6
+ import { scanDirectives, sourceLines, fenceState } from '../core/source.js'
7
+ import { CALLOUT_LABELS, labelSetFor } from '../core/labels.js'
8
+
9
+ function readDirective(src, name) {
10
+ if (!/^:::\s*[\w-]+/.test(src)) return null
11
+ const node = scanDirectives(src).nodes[0]
12
+ if (!node || node.end == null || (name && node.name !== name)) return null
13
+ return { ...node, raw: src.slice(0, node.end), body: src.slice(node.bodyStart, node.bodyEnd) }
14
+ }
15
+
16
+ function splitColumns(src) {
17
+ const columns = []
18
+ let start = 0, depth = 0, fence = null
19
+ for (const line of sourceLines(src)) {
20
+ const before = fence
21
+ fence = fenceState(line.text, fence)
22
+ if (before || fence) continue
23
+ if (/^:::\s*[\w-]+/.test(line.text)) depth++
24
+ else if (/^:::\s*$/.test(line.text)) depth--
25
+ if (!depth && /^\+\+\+[ \t]*$/.test(line.text)) {
26
+ columns.push(src.slice(start, line.start))
27
+ start = line.end
28
+ }
29
+ }
30
+ columns.push(src.slice(start))
31
+ return columns
32
+ }
33
+
34
+ function escapeHtml(text) {
35
+ return String(text).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;')
36
+ }
37
+
38
+ marked.use(markedKatex({ throwOnError: false, output: 'html' }))
39
+
40
+ // marked.use() prepends via unshift, so last-registered = highest priority.
41
+ // Register callout first (lowest priority) so columns and steps match before it.
42
+
43
+ let calloutLabels = CALLOUT_LABELS.en
44
+
45
+ // Ein Deck waehlt mit `lang` einen Satz Standardbeschriftungen und kann mit
46
+ // `callouts` einzelne davon ueberschreiben. Ohne Angabe bleibt es bei
47
+ // Englisch, damit bestehende Decks unveraendert aussehen. Ein Titel hinter
48
+ // dem Typ (`::: tip Eigener Titel`) sticht beides.
49
+ export function setCalloutLabels({ lang, callouts } = {}) {
50
+ // A regional code such as de-CH uses its language's titles.
51
+ const base = CALLOUT_LABELS[labelSetFor(lang)]
52
+ calloutLabels = { ...base, ...(callouts ?? {}) }
53
+ }
54
+
55
+ marked.use({
56
+ extensions: [{
57
+ name: 'callout',
58
+ level: 'block',
59
+ start(src) { return src.match(/^:::/m)?.index },
60
+ tokenizer(src) {
61
+ const m = readDirective(src)
62
+ if (!m) return
63
+ const token = { type: 'callout', raw: m.raw, calloutType: m.name.toLowerCase(), title: m.argument, tokens: [] }
64
+ this.lexer.blockTokens(m.body, token.tokens)
65
+ return token
66
+ },
67
+ renderer(token) {
68
+ const title = token.title || calloutLabels[token.calloutType] || token.calloutType
69
+ return `<div class="callout callout-${token.calloutType}"><div class="callout-title">${escapeHtml(title)}</div>${this.parser.parse(token.tokens)}</div>\n`
70
+ },
71
+ }],
72
+ })
73
+
74
+ marked.use({
75
+ extensions: [{
76
+ name: 'columns',
77
+ level: 'block',
78
+ start(src) { return src.match(/^:::columns\b/m)?.index },
79
+ tokenizer(src) {
80
+ const m = readDirective(src, 'columns')
81
+ if (!m) return
82
+ const colSrcs = splitColumns(m.body)
83
+ const token = { type: 'columns', raw: m.raw, columns: [] }
84
+ for (const colSrc of colSrcs) {
85
+ const colTokens = []
86
+ this.lexer.blockTokens(colSrc.trim(), colTokens)
87
+ token.columns.push(colTokens)
88
+ }
89
+ return token
90
+ },
91
+ renderer(token) {
92
+ const cols = token.columns
93
+ .map(colTokens => `<div class="column">${this.parser.parse(colTokens)}</div>`)
94
+ .join('\n')
95
+ return `<div class="columns">\n${cols}\n</div>\n`
96
+ },
97
+ }],
98
+ })
99
+
100
+ marked.use({
101
+ extensions: [{
102
+ name: 'steps',
103
+ level: 'block',
104
+ start(src) { return src.match(/^:::steps\b/m)?.index },
105
+ tokenizer(src) {
106
+ const m = readDirective(src, 'steps')
107
+ if (!m) return
108
+ const token = { type: 'steps', raw: m.raw, tokens: [] }
109
+ this.lexer.blockTokens(m.body, token.tokens)
110
+ return token
111
+ },
112
+ renderer(token) {
113
+ let counter = 0
114
+ const links = token.tokens.links || {}
115
+ const parts = []
116
+ for (const t of token.tokens) {
117
+ if (t.type === 'space') continue
118
+ if (t.type === 'list') {
119
+ const tag = t.ordered ? 'ol' : 'ul'
120
+ const items = t.items.map(item => {
121
+ const body = this.parser.parse(Object.assign([...item.tokens], { links }))
122
+ return `<li data-step="${counter++}">${body}</li>`
123
+ })
124
+ parts.push(`<${tag}>${items.join('')}</${tag}>`)
125
+ } else {
126
+ const html = this.parser.parse(Object.assign([t], { links }))
127
+ parts.push(`<div data-step="${counter++}">${html}</div>`)
128
+ }
129
+ }
130
+ return `<div class="steps-container">\n${parts.join('\n')}\n</div>\n`
131
+ },
132
+ }],
133
+ })
134
+
135
+ marked.use({
136
+ renderer: {
137
+ code({ text: code, lang: infoString }) {
138
+ const [lang = 'text', ...flags] = (infoString || '').split(/\s+/).filter(Boolean)
139
+ const attrs = flags.map(f => `${f}=""`).join(' ')
140
+ const encoded = (code || '')
141
+ .replace(/&/g, '&amp;')
142
+ .replace(/</g, '&lt;')
143
+ .replace(/>/g, '&gt;')
144
+ return `<codeblock lang="${lang}"${attrs ? ' ' + attrs : ''}>${encoded}</codeblock>`
145
+ },
146
+ },
147
+ })
@@ -0,0 +1,59 @@
1
+ .reader-view { position: fixed; inset: 0; display: grid; grid-template-rows: 48px 1fr; background: #101010; color: #e6e6e6; font: 14px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif; }
2
+ .reader-view * { box-sizing: border-box; }
3
+ /* Theme stylesheets style the document; the chrome must not inherit them. */
4
+ .reader-top, .reader-nav, .reader-bottom, .reader-toast, .reader-page-caption, .reader-page-notes,
5
+ .reader-top *, .reader-nav *, .reader-bottom *, .reader-toast *, .reader-page-caption * { font-family: system-ui, -apple-system, "Segoe UI", sans-serif; font-size: 14px; font-weight: 400; line-height: 1.5; letter-spacing: normal; text-transform: none; color: #e6e6e6; margin: 0; padding: 0; background: none; border: 0; }
6
+ .reader-top { display: flex; align-items: center; gap: 10px; padding: 0 14px !important; border-bottom: 1px solid #262626; background: #171717; min-width: 0; }
7
+ .reader-top h1 { font-size: 15px; font-weight: 600; margin: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: #fff; }
8
+ .reader-top .reader-meta { color: #a8a8a8; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
9
+ .reader-top .reader-spacer { flex: 1; }
10
+ .reader-top .reader-count { color: #b8b8b8; font-variant-numeric: tabular-nums; white-space: nowrap; }
11
+ .reader-btn { appearance: none; display: inline-flex; align-items: center; gap: 6px; padding: 6px 11px !important; border-radius: 6px; border: 1px solid #3a3a3a !important; background: #222 !important; color: #e6e6e6; cursor: pointer; font: inherit; text-decoration: none; white-space: nowrap; }
12
+ .reader-btn:hover { border-color: #5a5a5a; color: #fff; }
13
+ .reader-btn:disabled { opacity: 0.45; cursor: default; }
14
+ .reader-btn:disabled:hover { border-color: #3a3a3a; color: #e6e6e6; }
15
+ .reader-icon { flex: 0 0 auto; opacity: 0.85; }
16
+ .reader-btn.is-active .reader-icon { opacity: 1; }
17
+ .reader-btn.is-active { background: #2a2a2a !important; border-color: #6a6a6a !important; }
18
+ .reader-main { display: grid; grid-template-columns: 260px minmax(0, 1fr); min-height: 0; }
19
+ .reader-nav { overflow: auto; border-right: 1px solid #262626 !important; padding: 8px !important; }
20
+ .reader-nav ol { list-style: none; margin: 0; padding: 0; }
21
+ .reader-nav li { display: grid; grid-template-columns: 28px minmax(0, 1fr); gap: 8px; padding: 7px 8px !important; border-radius: 6px; cursor: pointer; color: #d6d6d6; align-items: baseline; }
22
+ .reader-nav li:hover { background: #1e1e1e; }
23
+ .reader-nav li.is-current { background: #23384f; color: #fff; }
24
+ .reader-nav li.is-chapter { margin-top: 8px; color: #fff; font-weight: 600; }
25
+ .reader-nav li .reader-num { color: #8a8a8a; font-variant-numeric: tabular-nums; font-size: 12px; }
26
+ .reader-nav li .reader-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
27
+ .reader-nav li .reader-part { display: block; font-size: 11px; font-weight: 400; color: #9a9a9a; text-transform: uppercase; letter-spacing: 0.06em; }
28
+ .reader-content { display: flex; flex-direction: column; min-height: 0; min-width: 0; background: #0b0b0b; }
29
+ .reader-stage-wrap { flex: 1; position: relative; min-height: 0; }
30
+ .reader-stage-wrap deck-stage { position: absolute !important; inset: 0 !important; width: 100%; height: 100%; }
31
+ .reader-bottom { display: flex; align-items: center; gap: 8px; padding: 8px 14px !important; border-top: 1px solid #262626; background: #171717; }
32
+ .reader-bottom .reader-spacer { flex: 1; }
33
+ .reader-read { flex: 1; overflow: auto; padding: 24px; display: flex; flex-direction: column; gap: 28px; align-items: center; }
34
+ .reader-page { width: min(100%, 1400px); }
35
+ .reader-page-frame { position: relative; width: 100%; overflow: hidden; box-shadow: 0 6px 30px rgba(0,0,0,0.5); border-radius: 4px; background: #000; }
36
+ .reader-page-scale { position: absolute; top: 0; left: 0; transform-origin: top left; }
37
+ .reader-page-scale > section.slide { width: 100%; height: 100%; }
38
+ .reader-read [data-step] { opacity: 1 !important; }
39
+ .reader-page-notes { margin: 10px 4px 0 !important; padding: 12px 16px !important; border-left: 3px solid #3d6094 !important; background: #161a20 !important; color: #d0d8e4; border-radius: 0 6px 6px 0; }
40
+ .reader-page-notes p { margin: 0 0 6px; }
41
+ .reader-page-caption { margin: 6px 4px 0; color: #9a9a9a; font-size: 12px; display: flex; gap: 8px; }
42
+ .reader-page-caption b { color: #d0d0d0; font-weight: 500; }
43
+ .reader-toast { position: fixed; bottom: 60px; left: 50%; transform: translateX(-50%); background: #27436a !important; color: #fff; padding: 8px 14px !important; border-radius: 6px; }
44
+ .reader-nav-toggle { display: none; }
45
+ @media (max-width: 820px) {
46
+ .reader-main { grid-template-columns: 1fr; }
47
+ .reader-nav { display: none; position: fixed; top: 48px; bottom: 0; left: 0; width: min(85vw, 320px); background: #141414; z-index: 4; box-shadow: 0 0 40px rgba(0,0,0,0.7); }
48
+ .reader-nav.is-open { display: block; }
49
+ .reader-nav-toggle { display: inline-flex; }
50
+ .reader-top .reader-meta, .reader-top .label-text { display: none; }
51
+ .reader-read { padding: 10px; }
52
+ }
53
+ @media print {
54
+ .reader-top, .reader-nav, .reader-bottom, .reader-toast { display: none !important; }
55
+ .reader-view, .reader-main, .reader-content, .reader-stage-wrap { position: static !important; display: block !important; background: none !important; }
56
+ }
57
+ .reader-entry { position: fixed; left: 14px; bottom: 14px; z-index: 50; padding: 6px 10px; border-radius: 6px; background: rgba(20,20,20,0.7); color: #fff; font: 13px system-ui, sans-serif; text-decoration: none; opacity: 0.25; transition: opacity 200ms; }
58
+ .reader-entry:hover { opacity: 1; }
59
+ @media print { .reader-entry { display: none !important; } }
@@ -0,0 +1,26 @@
1
+ import CodeBlock from '../components/CodeBlock.jsx'
2
+ import QrCodeTag from '../components/QrCodeTag.jsx'
3
+ import VideoPlayer from '../components/VideoPlayer.jsx'
4
+ import Poll from '../components/Poll.jsx'
5
+ import Question from '../components/Question.jsx'
6
+ import WordCloud from '../components/WordCloud.jsx'
7
+ import Scale from '../components/Scale.jsx'
8
+ import deckComponents from 'virtual:deck-components'
9
+
10
+ // Maps lowercase HTML tag names to Preact components.
11
+ // Add framework-wide components here and use them in slides.md as <tagname ...>.
12
+ //
13
+ // A deck can also ship its own components in a `components/` folder next to its
14
+ // .md file — those are picked up automatically and merged in below, so a widget
15
+ // that only one deck needs never becomes part of every other deck's bundle.
16
+ // Deck-local components win on a name collision.
17
+ export const registry = {
18
+ codeblock: CodeBlock,
19
+ qrcode: QrCodeTag,
20
+ videoplayer: VideoPlayer,
21
+ poll: Poll,
22
+ question: Question,
23
+ wordcloud: WordCloud,
24
+ scale: Scale,
25
+ ...deckComponents,
26
+ }
@@ -0,0 +1,32 @@
1
+ // Orders the positions windows of one talk send each other (presenter view
2
+ // ↔ audience windows). Messages can arrive late, after the receiver has
3
+ // already moved on; a position that is older than the receiver's own last
4
+ // change must not take it back. Every change carries a counter (a Lamport
5
+ // clock): a window ignores what is older than what it has. Two changes made
6
+ // at the same moment on both sides carry the same counter; then the window
7
+ // with the larger id wins on both sides, so they end up on the same slide.
8
+ const randomId = () => Array.from(crypto.getRandomValues(new Uint8Array(8)), b => b.toString(16).padStart(2, '0')).join('')
9
+
10
+ export function createOrder(id = randomId()) {
11
+ let latest = { seq: 0, from: id }
12
+ return {
13
+ id,
14
+ /** The stamp for a change made in this window. */
15
+ stamp() { latest = { seq: latest.seq + 1, from: id }; return { ...latest } },
16
+ /** This window's position as it is, for a window that just opened: taken unless it moved on. */
17
+ current() { return { ...latest, initial: true } },
18
+ /** Whether a position from another window is newer than this one's; it is then this window's too. */
19
+ accept(stamp) {
20
+ // A sender without stamps (an older page) is followed as before.
21
+ if (!Number.isInteger(stamp?.seq)) return true
22
+ if (stamp.seq < latest.seq) return false
23
+ // Before anyone has navigated, a new window takes the presenter's
24
+ // initial position regardless of its own id. Once navigation starts,
25
+ // replies and forwarded positions obey the same ordering as changes.
26
+ const opening = latest.seq === 0 && stamp.initial
27
+ if (stamp.seq === latest.seq && !opening && !(String(stamp.from) > latest.from)) return false
28
+ latest = { seq: stamp.seq, from: String(stamp.from) }
29
+ return true
30
+ },
31
+ }
32
+ }
@@ -0,0 +1,68 @@
1
+ import baseCSS from '../../assets/base.css?inline'
2
+ import { themes, palettes } from 'virtual:mdeck-extensions'
3
+ import { buildAppearance } from '../extensions/appearance.js'
4
+
5
+ const SELF_CONTAINED = typeof __MDECK_SELF_CONTAINED__ !== 'undefined'
6
+ && __MDECK_SELF_CONTAINED__
7
+
8
+ export const THEME_NAMES = Object.keys(themes)
9
+ export const THEME_METAS = Object.fromEntries(Object.entries(themes).map(([id, theme]) => [id, theme.manifest]))
10
+ export const PALETTES = Object.fromEntries(Object.entries(palettes).map(([id, palette]) => [id, palette.manifest]))
11
+
12
+ // The editor previews unsaved themes and palettes by handing their manifests
13
+ // (and, for themes, stylesheet text) to the runtime instead of the built
14
+ // module. Overrides win over registered extensions of the same id.
15
+ let overrides = { themes: {}, palettes: {} }
16
+ export function setExtensionOverrides(next = {}) {
17
+ overrides = { themes: next.themes ?? {}, palettes: next.palettes ?? {} }
18
+ }
19
+ function themeEntry(id) {
20
+ const override = overrides.themes[id]
21
+ if (override) return { manifest: override.manifest ?? override, load: () => typeof override.styles === 'string' ? Promise.resolve(override.styles) : themes[id]?.load() ?? Promise.resolve('') }
22
+ return themes[id]
23
+ }
24
+ // Every palette, with the editor's unsaved ones in place of the registered.
25
+ const allPalettes = () => ({ ...PALETTES, ...Object.fromEntries(Object.entries(overrides.palettes).map(([id, palette]) => [id, palette.manifest ?? palette])) })
26
+
27
+ function upsertStyle(id, textContent) {
28
+ let el = document.getElementById(id)
29
+ if (!el) {
30
+ el = document.createElement('style')
31
+ el.id = id
32
+ document.head.appendChild(el)
33
+ }
34
+ el.textContent = textContent
35
+ }
36
+
37
+ function syncThemeFonts(urls) {
38
+ for (const existing of [...document.querySelectorAll('link[data-deck-theme-font]')]) {
39
+ existing.remove()
40
+ }
41
+ for (const fontUrl of urls ?? []) {
42
+ const link = document.createElement('link')
43
+ link.rel = 'stylesheet'
44
+ link.href = fontUrl
45
+ link.setAttribute('data-deck-theme-font', '1')
46
+ document.head.appendChild(link)
47
+ }
48
+ }
49
+
50
+ export async function loadTheme({ theme = 'neue', palette, appearance, params = {}, meta = {} } = {}) {
51
+ const entry = themeEntry(theme)
52
+ if (!entry) throw new Error(`Unknown theme: "${theme}". Available: ${THEME_NAMES.join(', ')}`)
53
+
54
+ const styles = await entry.load()
55
+ const look = buildAppearance({
56
+ theme: entry.manifest, palettes: allPalettes(), palette, appearance,
57
+ params, meta, offline: SELF_CONTAINED,
58
+ })
59
+ for (const warning of look.warnings) console.warn(warning)
60
+
61
+ // Token defaults come from the manifest; the stylesheet only holds rules.
62
+ upsertStyle('deck-theme', [baseCSS, look.themeCss, styles].join('\n'))
63
+ // Offline-ready builds deliberately use the declared system-font fallbacks.
64
+ syncThemeFonts(look.fonts)
65
+ // The palette's colours, between the theme's tokens and the deck's params.
66
+ upsertStyle('deck-palette', look.paletteCss)
67
+ upsertStyle('deck-overrides', look.overridesCss)
68
+ }