mdeck 3.2.0 → 3.4.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 (67) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/assets/base.css +23 -2
  3. package/assets/extensions/themes/academic/styles.css +33 -5
  4. package/assets/extensions/themes/aurora/styles.css +32 -6
  5. package/assets/extensions/themes/minimal/styles.css +16 -4
  6. package/assets/extensions/themes/neue/styles.css +17 -4
  7. package/docs/reference/authoring.md +89 -6
  8. package/docs/site/content/audience.md +41 -4
  9. package/docs/site/content/commands.md +30 -4
  10. package/docs/site/content/drawing.md +1 -1
  11. package/docs/site/content/more-content.md +22 -0
  12. package/docs/site/content/sharing.md +1 -0
  13. package/docs/site/content/troubleshooting.md +1 -1
  14. package/docs/site/content/views.md +3 -1
  15. package/examples/citations/refs.bib +30 -0
  16. package/examples/citations/refs.yml +21 -0
  17. package/examples/citations/slides.md +43 -0
  18. package/examples/citations/slides.pdf +0 -0
  19. package/package.json +5 -1
  20. package/skills/write-slides/SKILL.md +7 -3
  21. package/src/build/bibliography.js +307 -0
  22. package/src/build/check.js +12 -0
  23. package/src/build/renderCheck.js +98 -0
  24. package/src/build/resultsPlugin.js +77 -0
  25. package/src/build/slidesPlugin.js +70 -7
  26. package/src/build/themeCheck.js +8 -5
  27. package/src/cli/args.js +22 -5
  28. package/src/cli/index.js +54 -12
  29. package/src/components/Bibliography.jsx +23 -0
  30. package/src/components/Numeric.jsx +59 -0
  31. package/src/components/Poll.jsx +59 -33
  32. package/src/components/QrCodeTag.jsx +11 -2
  33. package/src/components/Question.jsx +10 -6
  34. package/src/components/Scale.jsx +13 -8
  35. package/src/components/VideoPlayer.jsx +16 -0
  36. package/src/components/WordCloud.jsx +10 -6
  37. package/src/components/activity.jsx +90 -9
  38. package/src/components/citation.css +28 -0
  39. package/src/components/inlineText.js +40 -0
  40. package/src/components/numbers.js +39 -0
  41. package/src/components/poll.css +27 -7
  42. package/src/core/citations.js +108 -0
  43. package/src/core/editDeck.js +43 -0
  44. package/src/core/icons.js +2 -0
  45. package/src/core/ink.js +16 -0
  46. package/src/core/labels.js +48 -0
  47. package/src/core/parseSlides.js +1 -1
  48. package/src/core/results.js +64 -0
  49. package/src/core/urls.js +2 -2
  50. package/src/core/validateDeck.js +7 -1
  51. package/src/editor/sampleDeck.js +34 -8
  52. package/src/layouts/layoutApi.jsx +14 -3
  53. package/src/live/answer/answer.css +20 -0
  54. package/src/live/answer/answer.js +149 -24
  55. package/src/live/client.js +75 -7
  56. package/src/live/roomTag.js +6 -2
  57. package/src/live/rooms.js +6 -1
  58. package/src/runtime/ReaderView.jsx +2 -1
  59. package/src/runtime/citations.js +70 -0
  60. package/src/runtime/deck-stage.js +5 -2
  61. package/src/runtime/editorBridge.js +7 -1
  62. package/src/runtime/follow.js +89 -0
  63. package/src/runtime/ink/bus.js +7 -6
  64. package/src/runtime/ink/room.js +8 -0
  65. package/src/runtime/ink/select.js +4 -2
  66. package/src/runtime/main.jsx +22 -8
  67. package/src/runtime/registry.jsx +4 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.4.0 — 2026-10-08
4
+
5
+ - What follows a list has room above it in every theme, so a sentence after a list no longer sits right against its last item.
6
+ - Fixed: a bullet list inside a numbered list took numbers too, and in some themes the number sat on top of the bullet; it also pushed the numbering of the items after it. Every built-in theme now styles only a list's own items.
7
+ - A list inside a list item has room above it, as much as between its own items and less than between the outer ones, instead of sitting right against its item. Themes with their own spacing for nested lists keep it.
8
+ - Links and quotations look the part in every theme: links take the palette's accent colour instead of the browser's blue, and a `> quotation` is set in each theme's own way instead of as a bare indent: academic as an indented serif quotation, aurora with a quotation mark in its gradient, minimal and neue with a bar at its side (grey and red). A quotation's paragraphs now take the quotation's type, which themes that set every paragraph's font used to undo.
9
+ - The sample deck, on which `mdeck design`, the theme gallery and the theme checks show every theme, now also has a link, a quotation, a bullet list inside a numbered list, citations with their references at the foot of the slide, and a reference list.
10
+ - A poll's question and options may use Markdown and `$…$` maths, as on a slide: `options="$2x$|$x^2$|$\frac{x^3}{3}$"`. The slide draws them with KaTeX, the phones' answer page as MathML, which needs no fonts or scripts there; it keeps only text, emphasis and MathML from what the presenter's screen sends. A `|` inside a formula belongs to it (`$|x|$`), and `\|` is a bar in the text. Votes and `answer` still use the text as written. The same goes for the `question` of every activity (`<scale>`, `<question>`, `<wordcloud>`) and the scale's `low` and `high` labels. An answer page from an older `mdeck server` shows the source text.
11
+ - Fixed: the phones' answer page showed the word “null” below the question when there was no link for following the slides.
12
+ - Every activity has a lock button that closes it: answers after that do not count, and the phones say it is closed until it opens again. Together with the eye (`results="hidden"`) and the tick this makes a peer-instruction round: vote, close, show the bars, discuss, show the answer.
13
+ - Showing a poll's right answer also tells each phone whether its vote was right, or what the right answer is. The lock, the eye and the tick are kept in the activity room's state on the server, so every screen of the talk and the phones follow them, also after a reload.
14
+ - Polls, scales and numbers count “12 of 31 answered” while phones are connected.
15
+ - `multiple` lets each phone pick several options of a poll; the slide and the phones say so. `buttons="letters"` (or `"numbers"`) puts A, B, C before the options on the slide and shows only those on the phones, for options that are pictures or long formulas.
16
+ - New activity `<numeric>`: phones type a number (`0.5`, `0,5`, `1/2`), the slide shows the most frequent answers as bars. `answer` and `tolerance` mark the right ones and tell each phone whether it was right.
17
+ - The answers of a talk are kept beside the deck in `<slides>.results.json` while `mdeck run` runs: anonymous, phones numbered per question. Builds, `mdeck send` and `mdeck pdf` show them as the record of the talk (`--no-results` leaves them out), and `mdeck results` prints them as CSV.
18
+ - Fixed: a `>` or `<` in an activity's attributes (`question="Is $x > 0$?"`) cut its tag short, and the phones lost the options. Pictures in a tag's attributes, such as a poll's options, are now copied into folder builds.
19
+
20
+ ## 3.3.0 — 2026-10-08
21
+
22
+ - Citations and references: `bibliography: refs.bib` in the deck settings, then cite in Pandoc's syntax, `[@smith2020, p. 12]`, `[see @a; @b]`, `[-@a]` or `@smith2020` in a sentence. Each citation shows in the style's short form, each slide lists the full references of the works it cites above its footer (`show.citations: false` leaves them out), and `<bibliography />` lists them all on a slide (`part="1/2"` splits a long list). Reference files may be BibTeX (`.bib`), CSL-JSON (`.json`) or Hayagriva (`.yml`, Typst's format), several at once. `csl:` picks the style, `apa` by default, `vancouver`, `harvard1` or any `.csl` file; the style's words follow `lang`. `nocite:` lists works without citing them. Formatting happens at build time, so a deck carries only the finished text. Unknown keys are marked on the slide and reported by `mdeck check`.
23
+ - `mdeck check --render` looks at the deck in Chrome: it builds it, shows every slide with all steps revealed, and reports content that does not fit, text cut off at the slide's edge or hard to read on what is behind it, and errors the page reports, each with the slide's number, heading and line. `mdeck snapshot` saves slides as PNG files (`--slide 3,5-7`, `--dark`, `--light`, `-o`; by default in `.mdeck-snapshots/`), so a person or an assistant can look at them. The slide-writing skill uses both.
24
+ - New view `?view=follow`: people in the room follow the talk on their phone or laptop. It shows the slide the presenter shows, step by step, and their drawing, read from the stage room without sending anything; people can page back and return with **Back to live**, and never see a slide or step before the presenter shows it. `<qrcode follow />` shows its link on a slide, the phones' answer page links to it (**Follow the slides**), and `mdeck run --network` prints it. It works on the same network with `mdeck run --network`, and anywhere for a deck hosted with a `server` setting. Followers count as phones in the presenter's overview of who is connected.
25
+ - Fixed: a web video (YouTube, Vimeo, SwitchTube) in `<videoplayer>`'s click mode went on playing, with sound, after leaving its slide. Leaving the slide now stops it; coming back shows it ready to play.
26
+ - Straight lines snap to steps of 15° (horizontal, vertical, 45° …) when they are within 4° of one, both when a held stroke straightens and when an end point is dragged; further off they stay as drawn.
27
+ - Polls have a button with an eye that shows or hides the results (bars and counts), also in the audience window and on paired devices. `results="hidden"` starts with them hidden, e.g. for peer instruction; the audience then sees no bars, the presenter's own screens show them faint.
28
+ - `--no-polls` leaves slides with a poll, question, word cloud, scale or join code (`<qrcode join />`) out of `mdeck send`, `mdeck build --reader` and `mdeck pdf`. By default they stay as a record of the talk. Drawings stay on their slides.
29
+
3
30
  ## 3.2.0 — 2026-10-07
4
31
 
5
32
  - Pictures written in the text fit on the slide in every theme: a paragraph of nothing but pictures shrinks into the room below the heading instead of running off the slide, keeping its proportions (small pictures keep their size), and several pictures in one paragraph sit side by side. Such paragraphs have the class `pictures`, for themes that set them their own way; a picture in a sentence is left alone.
package/assets/base.css CHANGED
@@ -105,7 +105,10 @@ deck-stage[data-ink-hidden] .slide-ink { display: none; }
105
105
  .split-right { flex: 1; min-width: 0; display: flex; flex-direction: column; justify-content: safe center; gap: var(--gap-item); }
106
106
 
107
107
  /* ── List containers ─────────────────────────────────────────────────────── */
108
+ /* Lists have no margins of their own, and paragraphs only one below them, so
109
+ whatever follows a list needs room above it. */
108
110
  .slide-body :is(ul, ol) + :is(ul, ol) { margin-top: var(--gap-item); }
111
+ .slide-body :is(ul, ol) + :not(ul, ol) { margin-top: var(--gap-item); }
109
112
  /* Markers (::before), padding, and font styling are visual — defined per theme */
110
113
  .slide-body ul {
111
114
  list-style: none;
@@ -115,7 +118,7 @@ deck-stage[data-ink-hidden] .slide-ink { display: none; }
115
118
  flex-direction: column;
116
119
  gap: var(--gap-item);
117
120
  }
118
- .slide-body ul li { position: relative; }
121
+ .slide-body ul > li { position: relative; }
119
122
  .slide-body ol {
120
123
  list-style: none;
121
124
  padding: 0;
@@ -125,7 +128,10 @@ deck-stage[data-ink-hidden] .slide-ink { display: none; }
125
128
  flex-direction: column;
126
129
  gap: var(--gap-item);
127
130
  }
128
- .slide-body ol li { counter-increment: slide-ol; position: relative; }
131
+ .slide-body ol > li { counter-increment: slide-ol; position: relative; }
132
+ /* A list inside an item: closer to its item than items are to each other,
133
+ and as close inside. Themes with their own (same selector) win. */
134
+ .slide-body li :is(ul, ol) { margin-top: calc(var(--gap-item) * 0.5); gap: calc(var(--gap-item) * 0.5); }
129
135
 
130
136
  /* ── Full-bleed image ────────────────────────────────────────────────────── */
131
137
  .slide--full-bleed-image { padding: 0; }
@@ -159,6 +165,21 @@ deck-stage[data-ink-hidden] .slide-ink { display: none; }
159
165
  .slide-footnotes p { margin: 0; }
160
166
  .slide-footnotes .footnote-backref { display: none; }
161
167
 
168
+ /* ── Links and quotations ───────────────────────────────────────────────── */
169
+ /* Defaults in the palette's colours instead of the browser's blue and a bare
170
+ indent. :where keeps them weaker than any rule of the theme's own. */
171
+ :where(.slide) a { color: var(--accent); text-underline-offset: 0.15em; text-decoration-thickness: 0.06em; }
172
+ :where(.slide) blockquote {
173
+ margin: 0 0 var(--gap-item);
174
+ padding-left: 0.9em;
175
+ border-left: 4px solid var(--accent);
176
+ color: var(--ink-soft);
177
+ }
178
+ :where(.slide) blockquote > p:last-child { margin-bottom: 0; }
179
+ /* The quote's paragraphs take its type: themes set the face and size of
180
+ every paragraph, which would undo a quote set in its own. */
181
+ .slide-body blockquote > p { font: inherit; color: inherit; letter-spacing: inherit; }
182
+
162
183
  /* ── Code blocks ─────────────────────────────────────────────────────────── */
163
184
  .code-block { white-space: pre-wrap; overflow-x: hidden; }
164
185
 
@@ -180,12 +180,12 @@ strong { color: var(--ink); font-weight: 700; }
180
180
  the text size, so long lists stay compact
181
181
  ───────────────────────────────────────────── */
182
182
  .slide-body ul, .slide-body ol { max-width: 1560px; }
183
- .slide-body ul li,
184
- .slide-body ol li {
183
+ .slide-body ul > li,
184
+ .slide-body ol > li {
185
185
  line-height: 1.45;
186
186
  padding-left: 44px;
187
187
  }
188
- .slide-body ul li::before {
188
+ .slide-body ul > li::before {
189
189
  content: '';
190
190
  position: absolute;
191
191
  left: 4px;
@@ -195,7 +195,7 @@ strong { color: var(--ink); font-weight: 700; }
195
195
  background: var(--accent);
196
196
  clip-path: polygon(0 0, 100% 50%, 0 100%);
197
197
  }
198
- .slide-body ol li::before {
198
+ .slide-body ol > li::before {
199
199
  content: counter(slide-ol) '.';
200
200
  position: absolute;
201
201
  left: 0;
@@ -206,7 +206,7 @@ strong { color: var(--ink); font-weight: 700; }
206
206
  }
207
207
  /* Second level: an en dash, a little smaller */
208
208
  .slide-body li :is(ul, ol) { margin-top: 8px; gap: 6px; font-size: 0.88em; }
209
- .slide-body li ul li::before {
209
+ .slide-body li ul > li::before {
210
210
  width: 0.5em;
211
211
  height: 2px;
212
212
  top: 0.72em;
@@ -579,6 +579,21 @@ strong { color: var(--ink); font-weight: 700; }
579
579
  }
580
580
  .slide-footnotes li { line-height: 1.4; }
581
581
  .slide-body sup { color: var(--accent); font-weight: 600; }
582
+ .slide-citations { font-family: var(--font-body); }
583
+ .slide-citations:not(.slide-footnotes + .slide-citations) {
584
+ border-top: 0;
585
+ position: relative;
586
+ padding-top: 14px;
587
+ }
588
+ .slide-citations:not(.slide-footnotes + .slide-citations)::before {
589
+ content: '';
590
+ position: absolute;
591
+ top: 0;
592
+ left: 0;
593
+ width: 280px;
594
+ height: 1px;
595
+ background: var(--ink-soft);
596
+ }
582
597
 
583
598
  /* ─────────────────────────────────────────────
584
599
  CODE — a listing on the surface, a rule at its side
@@ -629,3 +644,16 @@ strong { color: var(--ink); font-weight: 700; }
629
644
  overflow-y: hidden;
630
645
  }
631
646
  .slide .katex-display > .katex { font-size: 1.2em; }
647
+
648
+ /* ─────────────────────────────────────────────
649
+ QUOTATIONS — the quote environment of a printed text: set in from
650
+ both sides, in the serif, without ornament
651
+ ───────────────────────────────────────────── */
652
+ .slide-body blockquote {
653
+ margin: 0 0 var(--gap-item);
654
+ padding: 0 64px;
655
+ border: 0;
656
+ font-family: var(--font-serif);
657
+ font-style: italic;
658
+ color: var(--ink-soft);
659
+ }
@@ -66,13 +66,13 @@ deck-stage[data-deck-static] .slide::before { animation: none; }
66
66
  /* Print and PDF: gradient text prints with a hairline box around it, and
67
67
  glows print as dark halos; solid colours instead. */
68
68
  @media print {
69
- em, .slide-footer > span:last-child:not(:first-child), .chapter-num, .slide-body ol li::before {
69
+ em, .slide-footer > span:last-child:not(:first-child), .chapter-num, .slide-body ol > li::before {
70
70
  background: none !important;
71
71
  -webkit-text-fill-color: currentColor !important;
72
72
  color: var(--aurora-a) !important;
73
73
  filter: none !important;
74
74
  }
75
- .slide-body ul li::before, .focus-eyebrow::before, .slide-header > span:last-child:not(:empty)::after { box-shadow: none !important; }
75
+ .slide-body ul > li::before, .focus-eyebrow::before, .slide-header > span:last-child:not(:empty)::after { box-shadow: none !important; }
76
76
  /* Frosted glass blurs what lies behind it, which prints as an image. */
77
77
  .slide *, .slide *::before { -webkit-backdrop-filter: none !important; backdrop-filter: none !important; }
78
78
  }
@@ -457,13 +457,13 @@ strong { color: var(--ink); font-weight: 700; }
457
457
  /* ─────────────────────────────────────────────
458
458
  LISTS — glowing dots, gradient numbers
459
459
  ───────────────────────────────────────────── */
460
- .slide-body ul li {
460
+ .slide-body ul > li {
461
461
  font-weight: 400;
462
462
  line-height: 1.35;
463
463
  padding-left: 46px;
464
464
  position: relative;
465
465
  }
466
- .slide-body ul li::before {
466
+ .slide-body ul > li::before {
467
467
  content: '';
468
468
  position: absolute;
469
469
  left: 0;
@@ -474,11 +474,11 @@ strong { color: var(--ink); font-weight: 700; }
474
474
  background: var(--aurora-gradient);
475
475
  box-shadow: 0 0 14px color-mix(in oklab, var(--aurora-a) 55%, transparent);
476
476
  }
477
- .slide-body ol li {
477
+ .slide-body ol > li {
478
478
  padding-left: 60px;
479
479
  position: relative;
480
480
  }
481
- .slide-body ol li::before {
481
+ .slide-body ol > li::before {
482
482
  content: counter(slide-ol);
483
483
  position: absolute;
484
484
  left: 0;
@@ -642,3 +642,29 @@ strong { color: var(--ink); font-weight: 700; }
642
642
  overflow-x: auto;
643
643
  overflow-y: hidden;
644
644
  }
645
+
646
+ /* ─────────────────────────────────────────────
647
+ QUOTATIONS — no pane, only a large quotation mark in the colours of
648
+ the lights beside the quote
649
+ ───────────────────────────────────────────── */
650
+ .slide-body blockquote {
651
+ position: relative;
652
+ margin: 0 0 var(--gap-item);
653
+ padding: 0 0 0 76px;
654
+ border: 0;
655
+ max-width: 1300px;
656
+ color: var(--ink);
657
+ }
658
+ .slide-body blockquote::before {
659
+ content: '\201C';
660
+ position: absolute;
661
+ left: 0;
662
+ top: -18px;
663
+ font-size: 120px;
664
+ font-weight: 800;
665
+ line-height: 1;
666
+ background: var(--aurora-gradient);
667
+ -webkit-background-clip: text;
668
+ background-clip: text;
669
+ color: transparent;
670
+ }
@@ -215,8 +215,8 @@ strong { color: var(--ink); font-weight: 600; }
215
215
  /* ─────────────────────────────────────────────
216
216
  LISTS — a short dash, plain numbers
217
217
  ───────────────────────────────────────────── */
218
- .slide-body ul li { line-height: 1.4; padding-left: 40px; position: relative; }
219
- .slide-body ul li::before {
218
+ .slide-body ul > li { line-height: 1.4; padding-left: 40px; position: relative; }
219
+ .slide-body ul > li::before {
220
220
  content: '';
221
221
  position: absolute;
222
222
  left: 0;
@@ -225,8 +225,8 @@ strong { color: var(--ink); font-weight: 600; }
225
225
  height: 2px;
226
226
  background: var(--muted);
227
227
  }
228
- .slide-body ol li { padding-left: 52px; position: relative; }
229
- .slide-body ol li::before {
228
+ .slide-body ol > li { padding-left: 52px; position: relative; }
229
+ .slide-body ol > li::before {
230
230
  content: counter(slide-ol) '.';
231
231
  position: absolute;
232
232
  left: 0;
@@ -340,3 +340,15 @@ strong { color: var(--ink); font-weight: 600; }
340
340
  overflow-x: auto;
341
341
  overflow-y: hidden;
342
342
  }
343
+
344
+ /* ─────────────────────────────────────────────
345
+ QUOTATIONS — a thin bar in the quiet grey, the quote set in a little,
346
+ in the softer ink
347
+ ───────────────────────────────────────────── */
348
+ .slide-body blockquote {
349
+ margin: 0 0 var(--gap-item);
350
+ padding: 2px 0 2px 28px;
351
+ border: 0;
352
+ border-left: 3px solid var(--muted);
353
+ color: var(--ink-soft);
354
+ }
@@ -336,13 +336,13 @@ strong { color: var(--ink); font-weight: 600; }
336
336
  /* ─────────────────────────────────────────────
337
337
  LISTS — square markers in the accent, numbers in the display face
338
338
  ───────────────────────────────────────────── */
339
- .slide-body ul li {
339
+ .slide-body ul > li {
340
340
  font-weight: 400;
341
341
  line-height: 1.32;
342
342
  padding-left: 44px;
343
343
  position: relative;
344
344
  }
345
- .slide-body ul li::before {
345
+ .slide-body ul > li::before {
346
346
  content: '';
347
347
  position: absolute;
348
348
  left: 0;
@@ -351,12 +351,12 @@ strong { color: var(--ink); font-weight: 600; }
351
351
  height: 14px;
352
352
  background: var(--accent);
353
353
  }
354
- .slide-body ol li {
354
+ .slide-body ol > li {
355
355
  counter-increment: slide-ol;
356
356
  padding-left: 64px;
357
357
  position: relative;
358
358
  }
359
- .slide-body ol li::before {
359
+ .slide-body ol > li::before {
360
360
  content: counter(slide-ol, decimal-leading-zero);
361
361
  position: absolute;
362
362
  left: 0;
@@ -487,3 +487,16 @@ strong { color: var(--ink); font-weight: 600; }
487
487
  overflow-x: auto;
488
488
  overflow-y: hidden;
489
489
  }
490
+
491
+ /* ─────────────────────────────────────────────
492
+ QUOTATIONS — a rule, not a box: a red bar to the left, the quote set
493
+ in a little, in the text's own size
494
+ ───────────────────────────────────────────── */
495
+ .slide-body blockquote {
496
+ margin: 0 0 var(--gap-item);
497
+ padding: 2px 0 2px 32px;
498
+ border: 0;
499
+ border-left: 6px solid var(--accent);
500
+ max-width: 1200px;
501
+ color: var(--ink);
502
+ }
@@ -63,6 +63,10 @@ Each `---` line starts a new slide. Frontmatter is only needed when you want a s
63
63
  | `params` | — | Theme-specific color/font overrides (see below) |
64
64
  | `reader.themes` | `true` | Whether the reader view offers a switch between light and dark (see below) |
65
65
  | `reader.notes` | `false` | Whether the reader view shows speaker notes under each slide in Read mode |
66
+ | `bibliography` | — | Reference file, or a list of them, relative to the deck: `.bib`, `.json` (CSL-JSON) or `.yml` (Hayagriva) (see [Citations](#citations-and-references)) |
67
+ | `csl` | `apa` | Citation style: `apa`, `vancouver`, `harvard1`, or a `.csl` file |
68
+ | `nocite` | — | References to list without citing them; `['*']` lists the whole file |
69
+ | `show.citations` | `true` | `false` leaves out each slide's own references above the footer |
66
70
 
67
71
  ### Theme params
68
72
 
@@ -87,7 +91,8 @@ reader:
87
91
  ```
88
92
 
89
93
  Views are chosen with one address parameter: `?view=deck`, `?view=reader`,
90
- `?view=presenter` or `?view=audience`. `mdeck send` produces a file that opens
94
+ `?view=presenter`, `?view=audience` or `?view=follow` (the presenter's slide and
95
+ ink on another device, read from the stage room; never ahead of the presenter). `mdeck send` produces a file that opens
91
96
  in the reader view (outline, Read mode, a light/dark switch, PDF download).
92
97
  The file carries only the deck's own theme and palette, with that theme's
93
98
  fonts, so readers see the deck as it was made; they can switch between its
@@ -376,6 +381,52 @@ This claim needs a source.[^1]
376
381
  [^1]: The source citation goes here.
377
382
  ```
378
383
 
384
+ ### Citations and references
385
+
386
+ Name a reference file in the deck frontmatter and cite its entries by key, in the syntax Pandoc and Quarto use:
387
+
388
+ ```markdown
389
+ ---
390
+ bibliography: refs.bib
391
+ csl: apa
392
+ ---
393
+ # Results
394
+
395
+ As @smith2020 showed, it works [see @smith2020, p. 12; @doe2019].
396
+
397
+ ---
398
+ # References
399
+
400
+ <bibliography />
401
+ ```
402
+
403
+ | Written | APA | Vancouver |
404
+ |---|---|---|
405
+ | `[@smith2020]` | (Smith et al., 2020) | (1) |
406
+ | `[@smith2020, p. 12]` | (Smith et al., 2020, p. 12) | (1) |
407
+ | `[see @smith2020; @doe2019]` | (see Smith et al., 2020; Doe, 2019) | (1,2) |
408
+ | `[-@smith2020]` | (2020) | (1) |
409
+ | `@smith2020` | Smith et al. (2020) | Smith et al. (1) |
410
+ | `@smith2020 [chap. 3]` | Smith et al. (2020, Chapter 3) | Smith et al. (1) |
411
+
412
+ - **On the slide** each citation shows in the style's short form, and the full reference of every work the slide cites stands in small type above the footer, after any footnotes. `show.citations: false` leaves those out.
413
+ - **`<bibliography />`** lists every cited work, plus those named in `nocite`, in the style's order. A list too long for one slide goes on several: `<bibliography part="1/2" />` on one and `<bibliography part="2/2" />` on the next split it in halves.
414
+ - **Locators** after a comma: `p.`/`pp.`, `chap.`, `sec.`, `fig.`, `vol.`, `para.`, `no.`, `l.`, `eq.` and the German `S.`, `Kap.`, `Abb.`, `Bd.`, `Nr.`; a bare number is a page.
415
+ - **`@key` without brackets** is a citation only when the key is in the reference file, so e-mail addresses and `@handles` stay text. Citations in code, in links' addresses and in `:::notes` blocks work as in slide text, except that notes show no reference footer.
416
+ - **A key that is not in the file** shows as `@key?`, marked, and `mdeck check` and `mdeck build` warn about it.
417
+
418
+ **Reference files.** `bibliography` takes one file or a list:
419
+
420
+ | Extension | Format | Comes from |
421
+ |---|---|---|
422
+ | `.bib` | BibTeX / BibLaTeX | Zotero, JabRef, Google Scholar, most journals |
423
+ | `.json` | CSL-JSON | Zotero ("CSL JSON"), Pandoc |
424
+ | `.yml`, `.yaml` | Hayagriva (Typst), or CSL-YAML when the file is a list | Typst projects |
425
+
426
+ **Styles.** `apa`, `vancouver` and `harvard1` are built in. Any other style from the [CSL style repository](https://www.zotero.org/styles) works as a file next to the deck: `csl: nature.csl`. The style's words (`p.`/`S.`, `and`/`und`, `et al.`/`u. a.`) follow the deck's `lang`; English, German, French, Spanish and Dutch are included, other languages fall back to English. Note styles (such as Chicago notes-bibliography) are written for footnotes; on slides an author-date or numeric style reads better.
427
+
428
+ The references are formatted when the deck is built, so the deck carries only the finished text and works offline; the dev server reformats them when the deck or a reference file changes.
429
+
379
430
  ---
380
431
 
381
432
  ## Code blocks
@@ -533,8 +584,28 @@ labels:
533
584
  | `respond.send` | Send | Senden |
534
585
  | `respond.sent` | Sent. Thank you! | Gesendet. Danke! |
535
586
  | `join.scan` | Scan to join | Scannen und mitmachen |
587
+ | `follow.scan` | Scan to follow the slides | Scannen und Folien mitverfolgen |
588
+ | `follow.open` | Follow the slides | Folien mitverfolgen |
589
+ | `follow.live` | Live | Live |
590
+ | `follow.back` | Back to live | Zur aktuellen Folie |
591
+ | `follow.waiting` | The talk has not started yet. | Der Vortrag hat noch nicht begonnen. |
592
+ | `follow.tryHere` | Try following here | Mitverfolgen hier ausprobieren |
536
593
  | `question.empty` | Answers appear here. | Hier erscheinen die Antworten. |
537
594
  | `scale.average` | Average {n} | Durchschnitt {n} |
595
+ | `poll.answeredOf` | {n} of {of} answered | {n} von {of} haben geantwortet |
596
+ | `poll.closed` | Closed | Geschlossen |
597
+ | `poll.several` | Several answers possible. | Mehrere Antworten möglich. |
598
+ | `poll.pickSeveral` | Tap all that apply. | Alle zutreffenden antippen. |
599
+ | `poll.thanksSeveral` | Thanks! You chose {choice}. Tap to change. | Danke! Gewählt: {choice}. Zum Ändern antippen. |
600
+ | `respond.closed` | Closed: no more answers. | Geschlossen: keine weiteren Antworten. |
601
+ | `respond.right` | Right! | Richtig! |
602
+ | `respond.wrong` | Not quite. The right answer: {answer} | Leider nicht. Richtig ist: {answer} |
603
+ | `respond.solution` | The right answer: {answer} | Richtig ist: {answer} |
604
+ | `numeric.placeholder` | A number | Eine Zahl |
605
+ | `numeric.invalid` | Enter a number, such as 0.5 or 1/2. | Eine Zahl eingeben, z. B. 0,5 oder 1/2. |
606
+ | `numeric.sent` | Sent: {value}. Send another to change. | Gesendet: {value}. Zum Ändern eine andere senden. |
607
+ | `numeric.other` | Other | Andere |
608
+ | `numeric.right` | {n} right | {n} richtig |
538
609
 
539
610
  ---
540
611
 
@@ -685,23 +756,35 @@ For `play="auto"` the video pauses and resets to the beginning when you navigate
685
756
  | Attribute | Default | Description |
686
757
  |---|---|---|
687
758
  | `room` | `poll` | Name of the room that collects the answers; unique per poll in a deck. Letters, digits, `.`, `-`, `_` |
688
- | `options` | — | Answers separated by `\|` |
689
- | `question` | — | Question text; phones fall back to the slide heading |
759
+ | `options` | — | Answers separated by `\|`. Markdown and `$…$` maths, as on a slide (see below) |
760
+ | `question` | — | Question text, Markdown and maths like `options`; phones fall back to the slide heading |
690
761
  | `qr` | `true` | `false` leaves out the QR code (shown earlier with `<qrcode join />`) |
691
- | `answer` | — | The right answer (several separated by `\|`): a button with a tick outlines its label, bar and count in green, also in the audience window and on paired devices. `--poll-correct` sets the colour |
762
+ | `answer` | — | The right answer (several separated by `\|`): a button with a tick outlines its label, bar and count in green, also in the audience window and on paired devices, and each phone says whether its vote was right. `--poll-correct` sets the colour |
763
+ | `multiple` | — | Each phone may pick several options; the slide and the phones say so. A vote is then the list of options picked, and is right when it is exactly the `answer` options |
764
+ | `buttons` | — | `letters` puts A, B, C … before the options, and the phones show only those; `numbers` does the same with 1, 2, 3 …. For options better read on the slide, such as pictures (`options="![](a.svg)\|![](b.svg)"`) |
765
+ | `results` | — | `hidden` starts with the results hidden, e.g. for peer instruction. A button with an eye shows or hides them (bars and counts) at any time, also in the audience window and on paired devices. Hidden, the audience window shows no bars; the presenter's own screens show them faint |
692
766
 
693
- Three more activities take the same `room`, `question` and `qr`:
767
+ Four more activities take the same `room`, `question` and `qr`:
694
768
 
695
769
  | Tag | Attributes | Phones | Slide |
696
770
  |---|---|---|---|
697
771
  | `<scale>` | `min` (1), `max` (5), `low`, `high` | One button per number, `low`/`high` as labels | Count per number and the average; latest answer per device |
772
+ | `<numeric>` | `answer`, `tolerance` (0), `limit` (6), `placeholder`, `results` | A number field: `0.5`, `0,5`, `1/2`, `1e-3`; latest answer per device | The most frequent answers as bars, the rest as Other; with `answer`, the tick marks the right ones, counts them and tells each phone |
698
773
  | `<wordcloud>` | `placeholder`, `limit` (60), `height` (520) | Text field, up to 40 characters, repeatable | A packed word cloud (d3-cloud): size by frequency, some words upright, case-insensitive |
699
774
  | `<question>` | `placeholder`, `limit` (8) | Text field, up to 200 characters, repeatable | The newest answers as cards |
700
775
 
701
- `<qrcode join />` shows the deck's join code large with its link (`size`, default 420), for a slide that invites everyone once; activities after it can use `qr="false"`. QR codes (`<qrcode>`, activities) are SVG in the slide's `--ink` on a transparent background; `--qr-ink` and `--qr-bg` override the colours.
776
+ `<qrcode follow />` shows the link for following the slides on phones and laptops (`?view=follow`), with `mdeck run --network` or a hosted deck with a `server` setting; the phones' answer page links to it as well. `<qrcode join />` shows the deck's join code large with its link (`size`, default 420), for a slide that invites everyone once; activities after it can use `qr="false"`. QR codes (`<qrcode>`, activities) are SVG in the slide's `--ink` on a transparent background; `--qr-ink` and `--qr-bg` override the colours.
777
+
778
+ Options and the question may use Markdown and maths, e.g. `options="$2x$|$x^2$|$\frac{x^3}{3}$"`. A `|` inside a formula belongs to it (`$|x|$` is one option), and `\|` is a bar in plain text. The slide draws maths with KaTeX, the phones as MathML; votes and `answer` use the text as written. The other activities' `question`, and the scale's `low` and `high`, take Markdown and maths too.
702
779
 
703
780
  The slide shows live bars, the number of answers, a QR code and a short link. Phones never load the deck: they open the server's own answer page at `<server>/<code>`, where the six-digit session code is the same for every poll in the deck. The presenter's screen (the presenter view or a full deck window, never an embedded preview) announces the poll on the current slide with what the phones should show and the deck's look; the server only accepts that from the presenter. Between polls the phones wait. Each device's latest vote counts. The presenter can reset the room (hover over the results).
704
781
 
782
+ Every activity has a lock button that closes it: answers after that do not count, and the phones say it is closed until it opens again. Polls and `<numeric>` also have the eye that hides or shows the results. Below the results, polls, scales and `<numeric>` count “12 of 31 answered” while phones are connected (people following the slides count as phones). The lock, the eye and the tick reach every screen of the talk, also after a reload: they are kept in the activity room's state on the server, which only the presenter may set, and phones read it there. Reset clears the answers and opens the activity again.
783
+
784
+ While `mdeck run` runs, the answers are kept beside the deck in `<slides>.results.json`: per activity room, each answer with its time, the phones numbered per room (their ids are not kept), and, once an activity was closed, only the answers that counted. Reset takes a room out of the file; a new, empty server leaves the file alone. Builds, `mdeck send` and `mdeck pdf` show the kept answers where no room server answers (`--no-results` leaves them out), and `mdeck results [slides.md] [-o file.csv]` prints them as CSV: `room,phone,at,answer`, several picks joined by `|`.
785
+
786
+ `--no-polls` leaves slides with an activity or a join code (`<qrcode join />`) out of `mdeck send`, `mdeck build --reader` and `mdeck pdf`; without it they show as a record of the talk. Saved drawings follow the remaining slides.
787
+
705
788
  Rooms run inside `mdeck run` (add `--network` so phones can reach it) or on a server started with `mdeck server`, set in the deck:
706
789
 
707
790
  ```yaml
@@ -28,19 +28,47 @@ Every poll in a presentation shows the same code, so people scan once. Their pho
28
28
  | Setting | Meaning |
29
29
  |---|---|
30
30
  | `room` | A short name for this question, different for each poll in the deck. Letters, digits, hyphens and underscores. |
31
- | `answer` | Optional: the right answer, written exactly like the option. A button with a tick then appears under the bars; tapping it outlines the right answer, its bar and its number in green, and tapping again hides it. The audience window shows it too. |
31
+ | `answer` | Optional: the right answer, written exactly like the option (several separated by `\|`). A button with a tick then appears under the bars; tapping it outlines the right answer, its bar and its number in green, and each phone says whether its vote was right. Tapping again hides it. |
32
32
  | `options` | The answers, separated by `\|` |
33
33
  | `question` | Optional question text. Without it, phones show the slide's heading. |
34
+ | `multiple` | Each phone may pick several options. The slide and the phones say “Several answers possible”. Without it, one option per phone. |
35
+ | `buttons` | `letters` puts A, B, C … before the options on the slide, and the phones show only those letters; `numbers` does the same with 1, 2, 3 … For options that are pictures or long formulas, better read on the big screen. |
36
+ | `results` | `hidden` starts with the bars hidden, e.g. to let people vote before they see what others chose. |
34
37
  | `qr` | `false` leaves out the QR code, when an earlier slide showed it with `<qrcode join />`. |
35
38
 
39
+ The question and the options may contain formatting and formulas, written as on a slide, so a maths lecture can ask:
40
+
41
+ ```markdown
42
+ <poll room="derivative" question="What is $\frac{d}{dx}\, x^2$?" options="$2x$|$x^2$|$\frac{x^3}{3}$" answer="$2x$" />
43
+ ```
44
+
45
+ Both the slide and the phones show the formulas. A `|` inside a formula stays in it, so `$|x|$` is one option; to put a `|` in plain text, write `\|`.
46
+
47
+ Options can also be pictures. Show only letters on the phones then:
48
+
49
+ ```markdown
50
+ <poll room="graph" question="Which graph shows $f(x) = e^{-x}$?" options="![](a.svg)|![](b.svg)|![](c.svg)" answer="![](b.svg)" buttons="letters" />
51
+ ```
52
+
53
+ ### During the poll
54
+
55
+ Below the bars, the slide counts the answers, “12 of 31 answered” while phones are connected. The presenter has three buttons there (the audience window does not show them):
56
+
57
+ - **The lock** closes the poll. Answers after that do not count, and the phones say the poll is closed until you open it again.
58
+ - **The eye** hides or shows the bars. Hidden, the audience sees no bars; your own screens show them faint.
59
+ - **The tick** (with `answer`) shows the right answer: on the slide, and on each phone, which says whether its answer was right.
60
+
61
+ Together they make a peer-instruction round: start with `results="hidden"`, let people vote, close, show the bars, let them discuss, and show the answer. **Reset** clears the answers and opens the poll again.
62
+
36
63
  With `lang: de` in the settings, the slide and the phones show German words, such as “Scannen und abstimmen”. The phones use the presentation's colours and fonts; if you pick another theme or palette in the presenter view, the phones change with it.
37
64
 
38
65
  ## Other kinds of questions
39
66
 
40
- Besides a poll, three more kinds work the same way: one line on a slide, answered on the phones.
67
+ Besides a poll, four more kinds work the same way: one line on a slide, answered on the phones.
41
68
 
42
69
  ```markdown
43
70
  <scale room="pace" min="1" max="5" low="Too slow" high="Too fast" />
71
+ <numeric room="limit" question="What is $\lim_{x \to 0} \frac{\sin x}{x}$?" answer="1" />
44
72
  <wordcloud room="mood" placeholder="One word" />
45
73
  <question room="ask" placeholder="Your question" />
46
74
  ```
@@ -48,10 +76,11 @@ Besides a poll, three more kinds work the same way: one line on a slide, answere
48
76
  | Tag | Phones show | The slide shows |
49
77
  |---|---|---|
50
78
  | `<scale>` | The numbers from `min` to `max` (1 to 5 unless set, at most 11 steps), with `low` and `high` as labels at the ends | How many chose each number, and the average. Each phone's latest answer counts |
79
+ | `<numeric>` | A field for a number: `0.5`, `0,5`, `1/2` or `1e-3` | The most frequent answers as bars, six unless `limit` says otherwise, and the rest as Other. With `answer`, the tick marks the right ones and counts them, and each phone hears whether it was right; `tolerance` (0 unless set) says how far off still counts. Each phone's latest answer counts |
51
80
  | `<wordcloud>` | A short text field | A cloud of every answer, packed around the middle, larger the more often it came in and some upright. Capitals and extra spaces do not matter. People may send several. `height` sets its height (520). Colours come from the palette; `--cloud-1` to `--cloud-6` override them |
52
81
  | `<question>` | A text field | The newest answers as cards, eight unless `limit` says otherwise. People may send several |
53
82
 
54
- All of them take `room`, `question` and `qr` like a poll, and `placeholder` for the text fields.
83
+ All of them take `room`, `question` and `qr` like a poll, and `placeholder` for the text fields. Their `question`, and the scale's `low` and `high`, may contain formatting and formulas like a poll's options. All of them have the lock that closes them; `<numeric>` also has the eye and the tick, and takes `results="hidden"`.
55
84
 
56
85
  ## Show the code once
57
86
 
@@ -73,6 +102,12 @@ Each question shows the QR code unless you say otherwise. To invite everybody on
73
102
 
74
103
  The code is drawn in the slide's colours, so it fits the theme; phone cameras also read light codes on dark slides.
75
104
 
105
+ ### Follow the slides on a phone
106
+
107
+ `<qrcode follow />` shows a code that opens your slides on people's phones and laptops, following you: the slide you show, step by step, and what you draw. They can page back to read something again and return with **Back to live**; slides you have not shown yet stay hidden. The phones' answer page links to it too (**Follow the slides**), so one join code can do both.
108
+
109
+ It needs their devices to reach your slides: present with `mdeck run --network` when everyone is on the same Wi-Fi (it prints the **Follow** address), or host the built deck with a `server` setting for people anywhere.
110
+
76
111
  Hover over the results to see **Reset**, which clears the answers, for example before the real session. Only the presenter can reset.
77
112
 
78
113
  ## How phones and slides meet
@@ -169,9 +204,11 @@ Two presentations with the same title share a session code. Give one of them `se
169
204
 
170
205
  Answers are anonymous. Each phone gets a random number so it can change its vote; nothing else identifies it. The server keeps answers in memory only. It forgets a room after twelve hours without visitors and everything when it restarts. Each phone can send a limited number of answers in a short time.
171
206
 
207
+ While you present with `mdeck run`, the answers are also kept beside the presentation, in `my-talk.results.json`. It holds each answer with its time, and the phones numbered 1, 2, 3 … per question instead of their random numbers. Once a question was closed, only the answers that counted. **Reset** takes a question's answers out of the file; a new server that starts empty leaves the file as it is. `mdeck results my-talk.md` prints the answers as CSV (question, phone, time, answer), to open in a spreadsheet; `-o answers.csv` saves them to a file. A deck hosted with its own `server` keeps nothing beside it.
208
+
172
209
  ## In a PDF or a shared file
173
210
 
174
- A PDF, a file made with `mdeck send` and a folder hosted without a poll server keep your questions as a record of the talk: each shows its question and options, with a note that it was answered live, and no join code. If the deck's `server` still answers when the file is made or opened, the results it holds appear instead. Printed slides never show the join code or the presenter's buttons.
211
+ A PDF, a file made with `mdeck send` and a folder hosted without a poll server keep your questions as a record of the talk, without a join code. With answers kept in `my-talk.results.json`, each shows its results as they were at the end of the talk; without, its question and options with a note that it was answered live. `--no-results` leaves the kept answers out. If the deck's `server` still answers when the file is opened, the results it holds appear instead. Printed slides never show the join code or the presenter's buttons.
175
212
 
176
213
  ## Make your own activity
177
214
 
@@ -16,7 +16,8 @@ For how the commands, the views and the servers fit together, see [Views, comman
16
16
  | `mdeck palettes search`, `install <palette>`, … | The same for palettes |
17
17
  | `mdeck design [my-talk.md or folder]` | Look at themes and palettes on a sample deck and fine-tune them; saves to `extensions/` |
18
18
  | `mdeck migrate my-talk.md --dry-run` | Preview the changes needed for API 2.0 |
19
- | `mdeck check my-talk.md` | Check settings and look for missing local files |
19
+ | `mdeck check my-talk.md` | Check settings and look for missing local files; `--render` also finds slides whose content does not fit |
20
+ | `mdeck snapshot my-talk.md` | Save pictures of the slides as PNG files, for you or your assistant to look at |
20
21
  | `mdeck send my-talk.md` | Make one file to send to readers, with a PDF inside |
21
22
  | `mdeck build my-talk.md` | Make a folder to host in `dist` |
22
23
  | `mdeck pdf my-talk.md` | Make a PDF |
@@ -86,15 +87,18 @@ The design page shows themes and palettes on a sample deck with every kind of sl
86
87
  | Command | Result |
87
88
  |---|---|
88
89
  | `mdeck build my-talk.md` | A folder in `dist` with every view, to host or copy |
89
- | `mdeck build my-talk.md --reader` | A hosted reader folder with speaker notes removed; add `--notes` to retain them |
90
+ | `mdeck build my-talk.md --reader` | A hosted reader folder with speaker notes removed; add `--notes` to retain them, `--no-polls` to leave out the slides with polls and their join codes |
90
91
  | `mdeck build my-talk.md -o talk.html` | Choose the output filename; local media stays alongside it |
91
92
  | `mdeck build my-talk.md --single-file -o talk.html` | Embed local images and media in one HTML file of the presentation |
92
93
  | `mdeck build my-talk.md --launchers` | Include presenter launchers in the folder build |
93
94
  | `mdeck send my-talk.md` | One file for readers: the reader view, speaker notes removed, a PDF inside |
94
95
  | `mdeck send my-talk.md --notes` | The same, keeping the speaker notes |
96
+ | `mdeck send my-talk.md --no-polls` | The same, leaving out the slides with polls and their join codes |
95
97
  | `mdeck send my-talk.md --no-pdf` | The same, without rendering the PDF |
96
- | `mdeck pdf my-talk.md -o talk.pdf` | Render the slides to a PDF file |
98
+ | `mdeck pdf my-talk.md -o talk.pdf` | Render the slides to a PDF file (`--no-polls` leaves out the slides with polls) |
97
99
  | `--no-drawings` | Leave out the drawings from `my-talk.drawings.json`, for `build`, `send` and `pdf` |
100
+ | `--no-results` | Leave out the answers kept in `my-talk.results.json`, for `build`, `send` and `pdf`; the polls then show their questions only |
101
+ | `mdeck results my-talk.md` | The answers kept during the talk, as CSV; `-o answers.csv` saves them to a file |
98
102
 
99
103
  See [Send, host or print your slides](sharing.html) for help choosing between them. Unknown options, missing option values and extra filenames stop the command before it changes any output. `--output=path` and `-o path` are both accepted. `mdeck preview --no-open --port 4200` previews without opening a browser and chooses a port.
100
104
 
@@ -117,7 +121,29 @@ mdeck check my-talk.md --strict
117
121
 
118
122
  The ordinary check fails when it finds an error. The strict check also fails when it finds a warning, such as a layout name it does not recognize.
119
123
 
120
- The check does not measure whether text fits on the slide. Always look through the preview too.
124
+ ## Check how the slides look
125
+
126
+ ```sh
127
+ mdeck check my-talk.md --render
128
+ ```
129
+
130
+ This builds the deck and shows every slide in Chrome, with all steps revealed as in a PDF. It reports, by slide, heading and line:
131
+
132
+ - content that is taller than the slide's room for it;
133
+ - text that is cut off at the slide's edge;
134
+ - text that is hard to read on a plain background behind it;
135
+ - errors the page reports, such as a component that fails.
136
+
137
+ The check takes a few seconds and needs Chrome or Chromium; set `MDECK_CHROME` to its path if it is not found. It checks the deck in its own appearance. A slide with a problem it cannot measure, such as text on a photo, still needs a look in the preview.
138
+
139
+ ## Pictures of the slides
140
+
141
+ ```sh
142
+ mdeck snapshot my-talk.md
143
+ mdeck snapshot my-talk.md --slide 3,5-7 --dark
144
+ ```
145
+
146
+ This saves each slide, with all steps revealed, as a 1280×720 PNG file in `.mdeck-snapshots/` beside the deck (`-o` for another folder), named `my-talk-03.png` and so on, and prints the paths. `--slide` picks slides by number; `--dark` and `--light` show that appearance instead of the deck's own. An AI assistant that reads pictures can use them to look at what it wrote. If you keep your slides in Git, add `.mdeck-snapshots/` to your `.gitignore`.
121
147
 
122
148
  ## Layouts, themes and palettes
123
149