mdeck 3.6.1 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +44 -2
  3. package/assets/extensions/palettes/cobalt/extension.toml +2 -0
  4. package/assets/extensions/palettes/greyscale/extension.toml +4 -12
  5. package/assets/extensions/palettes/lagoon/extension.toml +2 -0
  6. package/assets/extensions/palettes/nordic/extension.toml +2 -0
  7. package/assets/extensions/palettes/paper/extension.toml +2 -0
  8. package/assets/extensions/palettes/swiss/extension.toml +2 -0
  9. package/assets/extensions/themes/academic/styles.css +2 -4
  10. package/assets/extensions/themes/glass/styles.css +2 -6
  11. package/assets/extensions/themes/minimal/styles.css +2 -5
  12. package/assets/extensions/themes/neue/styles.css +2 -5
  13. package/assets/extensions/themes/plain/styles.css +1 -2
  14. package/assets/extensions/themes/work/styles.css +2 -5
  15. package/docs/reference/authoring.md +1 -1
  16. package/docs/reference/extensions.md +2 -0
  17. package/docs/reference/palettes.md +7 -4
  18. package/docs/site/assets/site.css +21 -0
  19. package/docs/site/assets/site.js +17 -7
  20. package/docs/site/build.js +15 -6
  21. package/docs/site/content/drawing.md +1 -1
  22. package/docs/site/content/sharing.md +1 -1
  23. package/docs/site/content/theme-authoring.md +6 -3
  24. package/docs/site/content/welcome.md +70 -6
  25. package/docs/site/examples/chart.svg +5 -0
  26. package/docs/site/examples/contours.jpg +0 -0
  27. package/docs/site/examples/first-talk.drawings.json +1 -0
  28. package/docs/site/examples/first-talk.md +125 -35
  29. package/docs/site/examples/first-talk.results.json +171 -0
  30. package/docs/site/examples/mdeck.svg +66 -0
  31. package/docs/site/examples/references.bib +23 -0
  32. package/docs/site/images/features/citations.webp +0 -0
  33. package/docs/site/images/features/code.webp +0 -0
  34. package/docs/site/images/features/drawing.webp +0 -0
  35. package/docs/site/images/features/polls.webp +0 -0
  36. package/docs/site/images/features/reader.webp +0 -0
  37. package/docs/site/images/features/themes.webp +0 -0
  38. package/docs/site/images/themes/academic-thumb.webp +0 -0
  39. package/docs/site/images/themes/glass-thumb.webp +0 -0
  40. package/docs/site/images/themes/minimal-thumb.webp +0 -0
  41. package/docs/site/images/themes/neue-thumb.webp +0 -0
  42. package/docs/site/images/themes/plain-thumb.webp +0 -0
  43. package/docs/site/images/themes/work-thumb.webp +0 -0
  44. package/package.json +3 -2
  45. package/skills/write-slides/SKILL.md +1 -1
  46. package/src/build/check.js +1 -0
  47. package/src/cli/themes.js +1 -1
  48. package/src/components/callout.css +11 -21
  49. package/src/core/labels.js +6 -0
  50. package/src/editor/components/PalettePicker.jsx +1 -1
  51. package/src/editor/extensions.js +2 -2
  52. package/src/extensions/contrast.js +3 -1
  53. package/src/extensions/manifest.js +7 -3
  54. package/src/extensions/packages.js +3 -0
  55. package/src/extensions/tokens.js +10 -2
  56. package/src/live/client.js +1 -1
  57. package/src/runtime/ReaderView.jsx +21 -12
  58. package/src/runtime/chrome.jsx +1 -1
  59. package/src/runtime/deck-stage.js +5 -1
  60. package/src/runtime/reader.css +2 -0
  61. package/docs/site/examples/notebook.svg +0 -58
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.7.0 — 2026-10-09
4
+
5
+ - Palettes have a third accent, `--accent-3`, and the three accents mean something: `--accent` is emphasis, `--accent-2` its companion (gradients, a contrast to the accent), `--accent-3` attention, used sparingly. `--accent-3` marks things and need not be readable as text, so it may be a yellow; `mdeck check` asks only that it can be seen (1.5:1 against the background). A palette without `--accent-3` keeps working and gets an amber (`#d99a00`, at night `#f5c542`); `mdeck check` suggests giving it its own. All built-in palettes and those in the theme repository set it (the repository requires it); `mdeck palettes update` brings installed ones up to date.
6
+ - Callouts take their colours from the palette: note, tip and important the accent, warning and caution the third accent. The five callout colours (`--callout-note` … `--callout-caution`) are gone, and with them the green, purple and blue that matched no palette. The built-in themes follow.
7
+
8
+ ## 3.6.2 — 2026-10-09
9
+
10
+ - The docs home page and the README start with what mdeck does: drawing from an iPad, polls from phones, citations, themes, running code and sending one file, each with a picture (`npm run feature-images` renders them). The docs' example deck is now a short tour of mdeck, with its own control bar and drawing, and a picker of every built-in theme as a picture, with light or dark.
11
+ - An embedded deck (`?embedded=1`) shows its control bar with `?controls=1`.
12
+ - The reader view has a **Drawings** button when the deck has drawings: it hides and shows them on the slides, in Read and in **Save as PDF**, and the browser remembers the choice. A file made with `mdeck send` opens there, so whoever gets it can see the slides clean.
13
+
3
14
  ## 3.6.1 — 2026-10-09
4
15
 
5
16
  - `mdeck snapshot --sheet` saves one picture of the slides in a grid, each under its number and heading (`my-talk-sheet.png`): a whole deck at a glance, for you or an assistant. It works with `--slide` and `--dark`.
package/README.md CHANGED
@@ -14,6 +14,47 @@
14
14
  <a href="LICENSE">MIT license</a>
15
15
  </p>
16
16
 
17
+ ## What you can do with it
18
+
19
+ <table>
20
+ <tr>
21
+ <td width="50%" valign="top">
22
+ <a href="docs/site/images/features/drawing.webp"><img src="docs/site/images/features/drawing.webp" alt="The presenter view on an iPad with circles, an arrow and a highlight drawn on a chart slide, and the projector behind it showing the same strokes"></a>
23
+ <p><b><a href="https://gh.tschieber.de/mdeck/drawing.html">Draw on your slides, from an iPad if you like</a></b><br>
24
+ Circle a number, underline a phrase, point with a laser. Pair an iPad by scanning a code, and it becomes your presenter screen and drawing pad while the laptop drives the projector: every stroke shows on the big screen as you draw.</p>
25
+ </td>
26
+ <td width="50%" valign="top">
27
+ <a href="docs/site/images/features/polls.webp"><img src="docs/site/images/features/polls.webp" alt="A poll slide with live bars and a QR code, and a phone showing the same question with one answer picked"></a>
28
+ <p><b><a href="https://gh.tschieber.de/mdeck/audience.html">Ask the room, and show the answers on the slide</a></b><br>
29
+ Polls, scales, word clouds, open questions and numbers to guess. People scan the code and answer on their phones, no app needed, and the bars grow as votes come in. Close the vote, discuss, reveal the right answer.</p>
30
+ </td>
31
+ </tr>
32
+ <tr>
33
+ <td width="50%" valign="top">
34
+ <a href="docs/site/images/features/citations.webp"><img src="docs/site/images/features/citations.webp" alt="A slide with author–year citations and the full references at its foot, over a slide that lists all references"></a>
35
+ <p><b><a href="https://gh.tschieber.de/mdeck/more-content.html#cite-a-source">Cite your sources properly</a></b><br>
36
+ Cite as in a paper, <code>[@knuth1984, p. 97]</code>, from the BibTeX, CSL-JSON or Hayagriva file you already keep. Each slide lists what it cites at its foot, in APA, Vancouver, Harvard or any CSL style.</p>
37
+ </td>
38
+ <td width="50%" valign="top">
39
+ <a href="docs/site/images/features/themes.webp"><img src="docs/site/images/features/themes.webp" alt="The same chart slide in six themes: neue, plain, work, academic, minimal and glass"></a>
40
+ <p><b><a href="https://gh.tschieber.de/mdeck/appearance.html">Write the content once, change the look anytime</a></b><br>
41
+ The file holds what you say; a theme and a palette decide how it looks. One line changes every slide, light or dark. Six themes come with mdeck, more are in the <a href="https://gh.tschieber.de/mdeck-themes/">theme repository</a>.</p>
42
+ </td>
43
+ </tr>
44
+ <tr>
45
+ <td width="50%" valign="top">
46
+ <a href="docs/site/images/features/code.webp"><img src="docs/site/images/features/code.webp" alt="A slide with a short Python example and its output below it"></a>
47
+ <p><b><a href="https://gh.tschieber.de/mdeck/more-content.html#show-a-code-example">Run code on the slide</a></b><br>
48
+ Give a Python or JavaScript example a Run button, change it in front of the audience and run it again. Python runs in the browser, NumPy and pandas included.</p>
49
+ </td>
50
+ <td width="50%" valign="top">
51
+ <a href="docs/site/images/features/reader.webp"><img src="docs/site/images/features/reader.webp" alt="The reader view of a talk on a laptop, with an outline of the slides, and on a phone in reading mode"></a>
52
+ <p><b><a href="https://gh.tschieber.de/mdeck/sharing.html">Send one file that anyone can open</a></b><br>
53
+ <code>mdeck send</code> makes a single HTML file: an outline, a reading mode for phones, light and dark, and the PDF inside. It opens in any browser, offline too, without your speaker notes.</p>
54
+ </td>
55
+ </tr>
56
+ </table>
57
+
17
58
  ## Start from what you have
18
59
 
19
60
  ```text
@@ -117,12 +158,13 @@ mdeck send talk.md # one file to email, with a PDF inside
117
158
  ## What you get
118
159
 
119
160
  - **Layouts:** title, chapter, big statement, image and text, split, full-bleed image, and plain content slides, with points that appear one at a time.
120
- - **Looks:** four built-in themes and five colour palettes, each light or dark, changeable without touching the slides. Install more from the [theme repository](https://gh.tschieber.de/mdeck-themes/) with `mdeck themes install`, or make your own as small `extension.toml` folders beside the deck.
161
+ - **Looks:** six built-in themes and six colour palettes, each light or dark, changeable without touching the slides. Install more from the [theme repository](https://gh.tschieber.de/mdeck-themes/) with `mdeck themes install`, or make your own as small `extension.toml` folders beside the deck.
121
162
  - **One launch page:** `mdeck run` opens a page with a live preview, the presenter and audience views, the deck and reader views, the editor, the guides, one-click builds and PDF, the deck's check results, and for decks with a server its health and the presenter code.
122
163
  - **Presenting:** a presenter view with notes and timer, and an audience window that stays in sync.
123
- - **Drawing on slides:** pen with pressure, highlighter and a fading marker, with a mouse, a finger or an iPad pencil (`D`). Drawings are saved in `<deck>.drawings.json` beside the deck and appear in every view, build and PDF. An iPad can present on its own, or pair with the laptop through a QR code on the launch page while the laptop's audience window follows it.
164
+ - **Drawing on slides:** pen with pressure, highlighter and a laser pointer, with a mouse, a finger or an iPad pencil (`D`). Drawings are saved in `<deck>.drawings.json` beside the deck and appear in every view, build and PDF. An iPad can present on its own, or pair with the laptop through a QR code on the launch page while the laptop's audience window follows it.
124
165
  - **Audience questions:** `<poll room="lunch" options="A|B|C" />` shows live results and a QR code; people vote on their phones. `<scale>`, `<wordcloud>` and `<question>` ask for a rating, words or open answers, and `<qrcode join />` shows the join code once for all of them. Rooms run inside `mdeck run --network` or on a small server (`mdeck server`).
125
166
  - **Sharing:** a reader view with an outline, a phone-friendly Read mode and a PDF download; speaker notes are stripped from shared builds, and interactive slides appear in their finished state.
167
+ - **Citations:** cite with `[@key]` from BibTeX, CSL-JSON or Hayagriva files; each slide lists its references at its foot, `<bibliography />` lists them all, in any CSL style.
126
168
  - **Content:** pictures, video, tables, callouts, code with syntax highlighting and live execution, formulas, QR codes, and your own Preact components, kept with one deck or shared between decks.
127
169
  - **New designs on request:** ask your assistant for a palette, theme, layout or interactive component; it keeps them as small folders beside the deck.
128
170
  - **Editing in the browser:** `mdeck edit talk.md` opens an editor with a live preview and forms for slides and settings, and `mdeck design` shows themes and palettes on a sample deck for fine-tuning. It writes back into your text file.
@@ -16,6 +16,7 @@ license = "MIT"
16
16
  "--rule" = "#d5dbf0"
17
17
  "--accent" = "#1f3fd1"
18
18
  "--accent-2" = "#e8590c"
19
+ "--accent-3" = "#e8a400"
19
20
  "--on-accent" = "#ffffff"
20
21
 
21
22
  [dark]
@@ -27,4 +28,5 @@ license = "MIT"
27
28
  "--rule" = "#212b5e"
28
29
  "--accent" = "#6f8cff"
29
30
  "--accent-2" = "#ff922b"
31
+ "--accent-3" = "#ffd43b"
30
32
  "--on-accent" = "#0a1030"
@@ -8,8 +8,8 @@ author = "Tilman Schieber"
8
8
  license = "MIT"
9
9
 
10
10
  # No colour anywhere: emphasis is black against the softer body text, the
11
- # second accent a mid grey; code and callouts use greys instead of their
12
- # usual colours.
11
+ # second accent a mid grey, the third (warnings) a lighter grey; code uses
12
+ # greys instead of its usual colours.
13
13
  [light]
14
14
  "--bg" = "#ffffff"
15
15
  "--surface" = "#f0f0f0"
@@ -19,6 +19,7 @@ license = "MIT"
19
19
  "--rule" = "#d4d4d4"
20
20
  "--accent" = "#000000"
21
21
  "--accent-2" = "#6e6e6e"
22
+ "--accent-3" = "#9a9a9a"
22
23
  "--on-accent" = "#ffffff"
23
24
  "--token-default" = "#141414"
24
25
  "--token-comment" = "#6b6b6b"
@@ -27,11 +28,6 @@ license = "MIT"
27
28
  "--token-keyword" = "#000000"
28
29
  "--token-function" = "#2a2a2a"
29
30
  "--token-operator" = "#555555"
30
- "--callout-note" = "#4a4a4a"
31
- "--callout-tip" = "#4a4a4a"
32
- "--callout-important" = "#000000"
33
- "--callout-warning" = "#7a7a7a"
34
- "--callout-caution" = "#000000"
35
31
 
36
32
  [dark]
37
33
  "--bg" = "#121212"
@@ -42,6 +38,7 @@ license = "MIT"
42
38
  "--rule" = "#303030"
43
39
  "--accent" = "#ffffff"
44
40
  "--accent-2" = "#a6a6a6"
41
+ "--accent-3" = "#6e6e6e"
45
42
  "--on-accent" = "#121212"
46
43
  "--token-default" = "#f2f2f2"
47
44
  "--token-comment" = "#9a9a9a"
@@ -50,8 +47,3 @@ license = "MIT"
50
47
  "--token-keyword" = "#ffffff"
51
48
  "--token-function" = "#e0e0e0"
52
49
  "--token-operator" = "#b0b0b0"
53
- "--callout-note" = "#b8b8b8"
54
- "--callout-tip" = "#b8b8b8"
55
- "--callout-important" = "#ffffff"
56
- "--callout-warning" = "#8c8c8c"
57
- "--callout-caution" = "#ffffff"
@@ -16,6 +16,7 @@ license = "MIT"
16
16
  "--rule" = "#e0deda"
17
17
  "--accent" = "#0d9488"
18
18
  "--accent-2" = "#c2410c"
19
+ "--accent-3" = "#ca8a04"
19
20
  "--on-accent" = "#ffffff"
20
21
 
21
22
  [dark]
@@ -27,4 +28,5 @@ license = "MIT"
27
28
  "--rule" = "#23332f"
28
29
  "--accent" = "#2dd4bf"
29
30
  "--accent-2" = "#fb923c"
31
+ "--accent-3" = "#facc15"
30
32
  "--on-accent" = "#062b26"
@@ -16,6 +16,7 @@ license = "MIT"
16
16
  "--rule" = "#cdd8e1"
17
17
  "--accent" = "#1f6f9f"
18
18
  "--accent-2" = "#7a5c9e"
19
+ "--accent-3" = "#c08a1e"
19
20
  "--on-accent" = "#ffffff"
20
21
 
21
22
  [dark]
@@ -27,4 +28,5 @@ license = "MIT"
27
28
  "--rule" = "#22343f"
28
29
  "--accent" = "#7cc4ea"
29
30
  "--accent-2" = "#c3a8e6"
31
+ "--accent-3" = "#e8c170"
30
32
  "--on-accent" = "#0e1a24"
@@ -16,6 +16,7 @@ license = "MIT"
16
16
  "--rule" = "#e0ddd6"
17
17
  "--accent" = "#c2410c"
18
18
  "--accent-2" = "#1f2937"
19
+ "--accent-3" = "#c99700"
19
20
  "--on-accent" = "#ffffff"
20
21
 
21
22
  [dark]
@@ -27,4 +28,5 @@ license = "MIT"
27
28
  "--rule" = "#2e2e2c"
28
29
  "--accent" = "#fb8c4a"
29
30
  "--accent-2" = "#e7e5e0"
31
+ "--accent-3" = "#f2c94c"
30
32
  "--on-accent" = "#151515"
@@ -16,6 +16,7 @@ license = "MIT"
16
16
  "--rule" = "#dadada"
17
17
  "--accent" = "#e30613"
18
18
  "--accent-2" = "#0047bb"
19
+ "--accent-3" = "#f2b700"
19
20
  "--on-accent" = "#ffffff"
20
21
 
21
22
  [dark]
@@ -27,4 +28,5 @@ license = "MIT"
27
28
  "--rule" = "#2e2e2e"
28
29
  "--accent" = "#ff3b30"
29
30
  "--accent-2" = "#6f9bff"
31
+ "--accent-3" = "#ffd60a"
30
32
  "--on-accent" = "#0d0d0d"
@@ -476,11 +476,9 @@ strong { color: var(--ink); font-weight: 700; }
476
476
  .slide-body .callout p { color: var(--ink); }
477
477
  .slide-body .callout :is(ul, ol) { gap: 8px; }
478
478
 
479
- .slide-body :is(.callout-theorem, .callout-lemma, .callout-corollary, .callout-proposition, .callout-tip, .callout-important) { --block: var(--accent); }
479
+ .slide-body :is(.callout-theorem, .callout-lemma, .callout-corollary, .callout-proposition, .callout-note, .callout-tip, .callout-important) { --block: var(--accent); }
480
480
  .slide-body :is(.callout-definition) { --block: var(--accent-2); }
481
- .slide-body .callout-note { --block: var(--callout-note); }
482
- .slide-body .callout-warning { --block: var(--callout-warning); }
483
- .slide-body .callout-caution { --block: var(--callout-caution); }
481
+ .slide-body :is(.callout-warning, .callout-caution) { --block: var(--accent-3); }
484
482
  .slide-body :is(.callout-theorem, .callout-lemma, .callout-corollary, .callout-proposition) > :not(.callout-title) {
485
483
  font-family: var(--font-serif);
486
484
  font-style: italic;
@@ -640,13 +640,9 @@ deck-stage[data-deck-static] .slide { animation: none; }
640
640
  box-shadow: 0 0 10px currentColor;
641
641
  }
642
642
  .slide-body .callout p { padding-left: 0; margin: 0; color: var(--ink); }
643
- .slide-body .callout-note { --tint: var(--callout-note); }
644
- .slide-body .callout-tip { --tint: var(--callout-tip); }
645
- .slide-body .callout-important { --tint: var(--callout-important); }
646
- .slide-body .callout-warning { --tint: var(--callout-warning); }
647
- .slide-body .callout-caution { --tint: var(--callout-caution); }
643
+ .slide-body :is(.callout-note, .callout-tip, .callout-important) { --tint: var(--accent); }
644
+ .slide-body :is(.callout-warning, .callout-caution) { --tint: var(--accent-3); }
648
645
  .slide-body .callout-definition { --tint: var(--accent-2); }
649
- .slide-body .callout-tip .callout-title { color: color-mix(in oklab, var(--callout-tip) 60%, var(--ink)); }
650
646
  .slide-body :is(.callout-theorem, .callout-lemma, .callout-corollary) .callout-title { color: var(--em); }
651
647
  .slide-body .callout-definition .callout-title { color: light-dark(color-mix(in oklab, var(--accent-2) 80%, var(--ink)), var(--accent-2)); }
652
648
 
@@ -297,11 +297,8 @@ strong { color: var(--ink); font-weight: 600; }
297
297
  margin: 32px 0 0;
298
298
  max-width: 1200px;
299
299
  }
300
- .slide-body .callout-tip { border-left-color: var(--accent); }
301
- .slide-body .callout-note { border-left-color: var(--callout-note); }
302
- .slide-body .callout-important { border-left-color: var(--callout-important); }
303
- .slide-body .callout-warning { border-left-color: var(--callout-warning); }
304
- .slide-body .callout-caution { border-left-color: var(--callout-caution); }
300
+ .slide-body :is(.callout-note, .callout-tip, .callout-important) { border-left-color: var(--accent); }
301
+ .slide-body :is(.callout-warning, .callout-caution) { border-left-color: var(--accent-3); }
305
302
  .slide-body .callout-title { margin-bottom: 8px; }
306
303
  .slide-body .callout p { padding-left: 0; color: var(--ink); font-size: var(--fs-body); }
307
304
  /* Lecture blocks: theorems in the accent, definitions in the second */
@@ -434,12 +434,9 @@ strong { color: var(--ink); font-weight: 600; }
434
434
  margin: 40px 0 0;
435
435
  max-width: 1200px;
436
436
  }
437
- .slide-body .callout-tip { border-top-color: var(--accent); }
437
+ .slide-body :is(.callout-note, .callout-tip, .callout-important) { border-top-color: var(--accent); }
438
438
  .slide-body .callout-tip .callout-title { color: var(--accent); }
439
- .slide-body .callout-note { border-top-color: var(--callout-note); }
440
- .slide-body .callout-important { border-top-color: var(--callout-important); }
441
- .slide-body .callout-warning { border-top-color: var(--callout-warning); }
442
- .slide-body .callout-caution { border-top-color: var(--callout-caution); }
439
+ .slide-body :is(.callout-warning, .callout-caution) { border-top-color: var(--accent-3); }
443
440
  .slide-body .callout-title { letter-spacing: 0.12em; margin-bottom: 10px; }
444
441
  .slide-body .callout p { padding-left: 0; color: var(--ink); font-size: var(--fs-body); }
445
442
  /* Lecture blocks: theorems under a rule in the accent, definitions in the second */
@@ -418,8 +418,7 @@ strong { font-weight: 600; color: var(--ink); }
418
418
  }
419
419
  .slide-body .callout p { padding-left: 0; margin: 0; color: var(--ink-soft); }
420
420
  .slide-body .callout :is(.callout-title) { color: var(--ink); }
421
- .slide-body .callout:is(.callout-warning, .callout-caution) { border-left-color: var(--callout-warning); }
422
- .slide-body .callout.callout-caution { border-left-color: var(--callout-caution); }
421
+ .slide-body .callout:is(.callout-warning, .callout-caution) { border-left-color: var(--accent-3); }
423
422
  .slide-body .callout.callout-proof { background: none; border: 0; padding: 0; }
424
423
 
425
424
  /* ─────────────────────────────────────────────
@@ -448,11 +448,8 @@ strong { font-weight: 600; color: var(--ink); }
448
448
  margin: 0 0 8px 0;
449
449
  }
450
450
  .slide-body .callout p { padding-left: 0; margin: 0; color: var(--ink); }
451
- .slide-body .callout-note { --bar: var(--callout-note); }
452
- .slide-body .callout-tip { --bar: var(--callout-tip); }
453
- .slide-body .callout-important { --bar: var(--callout-important); }
454
- .slide-body .callout-warning { --bar: var(--callout-warning); }
455
- .slide-body .callout-caution { --bar: var(--callout-caution); }
451
+ .slide-body :is(.callout-note, .callout-tip, .callout-important) { --bar: var(--accent); }
452
+ .slide-body :is(.callout-warning, .callout-caution) { --bar: var(--accent-3); }
456
453
  .slide-body .callout-definition { --bar: var(--accent-2); }
457
454
 
458
455
  /* ─────────────────────────────────────────────
@@ -686,7 +686,7 @@ A local SVG whose source contains `var(--…)` is drawn inline instead of as an
686
686
  layout's `image` (`title`, `chapter`, `image-text`, `full-bleed-image`, and
687
687
  local layouts that render it with `Picture` from `mdeck/layout`). It can use the
688
688
  palette's custom properties (`--bg`, `--surface`, `--ink`, `--ink-soft`,
689
- `--muted`, `--rule`, `--accent`, `--accent-2`, `--on-accent`) and follows the
689
+ `--muted`, `--rule`, `--accent`, `--accent-2`, `--accent-3`, `--on-accent`) and follows the
690
690
  theme, palette and appearance. Give each a fallback (`var(--accent, #1f3fd1)`)
691
691
  for places that show the file on its own, set `fit: contain` to keep the whole
692
692
  drawing, prefix class names in its `<style>` because inline styles apply to the
@@ -83,6 +83,7 @@ description = "Deep blue with a warm highlight."
83
83
  "--rule" = "#c9d7e2"
84
84
  "--accent" = "#0a6aa8"
85
85
  "--accent-2" = "#b45309"
86
+ "--accent-3" = "#c99700"
86
87
  "--on-accent" = "#ffffff"
87
88
 
88
89
  [dark]
@@ -94,6 +95,7 @@ description = "Deep blue with a warm highlight."
94
95
  "--rule" = "#24384b"
95
96
  "--accent" = "#ffbd69"
96
97
  "--accent-2" = "#7cc4ff"
98
+ "--accent-3" = "#f2c94c"
97
99
  "--on-accent" = "#102030"
98
100
  ```
99
101
 
@@ -23,7 +23,7 @@ The presenter view and the reader view switch palette and light or dark without
23
23
  | `swiss` | white, black and signal red | black, white and red |
24
24
  | `nordic` | ice grey, deep navy and a cool steel blue | polar night with ice blue |
25
25
  | `cobalt` | strong ultramarine on white, vivid orange | midnight blue, bright blue and orange |
26
- | `greyscale` | black, white and greys only, code and callouts too | the same, inverted |
26
+ | `greyscale` | black, white and greys only, code too | the same, inverted |
27
27
  | `lagoon` | teal on warm stone, orange second accent | deep teal-black, bright teal |
28
28
  | `paper` | off-white paper and near-black ink with one orange accent | charcoal with a softer orange |
29
29
 
@@ -35,7 +35,7 @@ More palettes are in the [theme repository](https://github.com/tilman-schieber/m
35
35
 
36
36
  ## Colours
37
37
 
38
- Every variant sets all nine:
38
+ Every variant sets all ten (a palette without `--accent-3` gets an amber, and `mdeck check` suggests setting one):
39
39
 
40
40
  | Colour | Role |
41
41
  |---|---|
@@ -45,8 +45,9 @@ Every variant sets all nine:
45
45
  | `--ink-soft` | Body text |
46
46
  | `--muted` | Small labels: headers, footers, captions |
47
47
  | `--rule` | Lines and dividers |
48
- | `--accent` | The highlight: emphasis, markers, chapter slides |
49
- | `--accent-2` | A second highlight for themes that pair two |
48
+ | `--accent` | Emphasis: highlighted words, markers, chapter slides; notes, tips and important callouts |
49
+ | `--accent-2` | The accent's companion: gradients, a contrast to the accent, definitions |
50
+ | `--accent-3` | Attention: warnings and cautions, and a colour that pops, used sparingly. It marks things (bars, shapes) and need not be readable as text, so it may be a yellow |
50
51
  | `--on-accent` | Text set on the accent, such as a chapter slide's title |
51
52
 
52
53
  Themes may mix them (for example a light tint of the accent), and read the other variant as `--inverse-bg`, `--inverse-ink`, `--inverse-accent` and so on.
@@ -71,6 +72,7 @@ description = "Sea blue and rust on chalk white."
71
72
  "--rule" = "#c9d7e2"
72
73
  "--accent" = "#0a6aa8"
73
74
  "--accent-2" = "#b45309"
75
+ "--accent-3" = "#c99700"
74
76
  "--on-accent" = "#ffffff"
75
77
 
76
78
  [dark]
@@ -82,6 +84,7 @@ description = "Sea blue and rust on chalk white."
82
84
  "--rule" = "#24384b"
83
85
  "--accent" = "#5fb3f0"
84
86
  "--accent-2" = "#f59e5b"
87
+ "--accent-3" = "#f2c94c"
85
88
  "--on-accent" = "#0d1a24"
86
89
  ```
87
90
 
@@ -68,6 +68,16 @@ h1 { font-size: clamp(32px, 3vw, 46px); margin: 0 0 24px; max-width: 23ch; }
68
68
  .welcome h1 { font-size: clamp(40px, 4.5vw, 63px); line-height: 1.08; max-width: 14ch; letter-spacing: -.055em; }
69
69
  .welcome article > p:first-of-type { font-size: 20px; line-height: 1.55; color: var(--secondary); max-width: 53ch; }
70
70
  .welcome article > p:nth-of-type(3) > a { display: inline-block; background: var(--blue); color: white; text-decoration: none; padding: 10px 19px; border-radius: 5px; font-size: 15px; font-weight: 600; }
71
+ .welcome article { max-width: 820px; }
72
+ .welcome article > :not(.features) { max-width: 76ch; }
73
+ .features { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 36px 32px; margin: 24px 0 32px; }
74
+ .features section { display: flex; flex-direction: column; }
75
+ .features section > p:first-child { order: -1; margin: 0 0 14px; }
76
+ .features section > p:first-child a { margin: 0; }
77
+ .features img { aspect-ratio: 9 / 5; object-fit: contain; background: #e9ecef; }
78
+ .features h3 { margin: 0 0 8px; font-size: 18px; line-height: 1.3; }
79
+ .features section > p:last-child { margin: 0; font-size: 15px; line-height: 1.55; }
80
+ @media (max-width: 720px) { .features { grid-template-columns: 1fr; } }
71
81
  h2 { font-size: 28px; margin: 52px 0 17px; }
72
82
  h3 { font-size: 21px; margin: 32px 0 12px; }
73
83
  p { margin: 0 0 20px; }
@@ -104,8 +114,19 @@ blockquote p:last-child { margin: 0; }
104
114
  .slide-example iframe { display: block; width: calc(100% - 36px); aspect-ratio: 16 / 9; margin: 0 18px; border: 0; background: white; box-shadow: 0 3px 9px #20314815; }
105
115
  .preview-controls { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 13px 18px; font-size: 12px; }
106
116
  .preview-controls > div { display: flex; align-items: center; gap: 7px; }
117
+ .theme-picker { display: grid; grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); gap: 6px; padding: 0 14px 14px; }
118
+ .theme-picker button { min-width: 0; display: flex; flex-direction: column; gap: 5px; align-items: stretch; padding: 4px 4px 6px; border: 2px solid transparent; border-radius: 7px; background: transparent; font-size: 12px; color: #344d80; cursor: pointer; }
119
+ .theme-picker img { display: block; width: 100%; height: auto; aspect-ratio: 16 / 9; border-radius: 4px; box-shadow: 0 1px 4px #20314826; border: 0; }
120
+ .theme-picker button:hover { border-color: #bdcce9; }
121
+ .theme-picker button[aria-checked="true"] { border-color: var(--blue); background: white; color: var(--blue); font-weight: 600; }
122
+ .appearance-switch { display: flex; }
123
+ .appearance-switch button { border-radius: 0; }
124
+ .appearance-switch button:first-child { border-radius: 4px 0 0 4px; }
125
+ .appearance-switch button:last-child { border-radius: 0 4px 4px 0; margin-left: -1px; }
126
+ .appearance-switch button[aria-checked="true"] { background: var(--blue); border-color: var(--blue); color: white; }
107
127
  .preview-controls button, .preview-controls select { padding: 5px 9px; border: 1px solid #bdcce9; background: white; border-radius: 4px; font-size: 12px; }
108
128
  .preview-controls button:hover { border-color: var(--blue); color: var(--blue); }
129
+ .preview-controls .appearance-switch button[aria-checked="true"]:hover { color: white; filter: brightness(.9); }
109
130
  .preview-controls output { margin-left: 5px; color: #344d80; }
110
131
  .preview-controls label { display: flex; align-items: center; gap: 7px; color: #344d80; }
111
132
  .page-outline { position: sticky; top: 120px; align-self: start; font-size: 12px; max-height: calc(100dvh - 150px); overflow: auto; padding-left: 18px; border-left: 1px solid var(--rule); margin-top: 53px; }
@@ -79,21 +79,31 @@ for (const button of document.querySelectorAll('[data-copy]')) {
79
79
 
80
80
  for (const example of document.querySelectorAll('[data-preview]')) {
81
81
  const frame = example.querySelector('iframe')
82
- const output = example.querySelector('output')
83
82
  let index = 0
84
- for (const button of example.querySelectorAll('[data-control]')) {
85
- button.addEventListener('click', () => frame.contentWindow.postMessage({ deckControl: { command: button.dataset.control } }, location.origin))
83
+ // Theme pictures and light or dark: the deck reloads in the new look on
84
+ // the same slide. Light or dark starts on each theme's own.
85
+ const themes = [...example.querySelectorAll('[data-theme]')]
86
+ const switches = [...example.querySelectorAll('[data-set-appearance]')]
87
+ let theme = themes.find(button => button.getAttribute('aria-checked') === 'true') ?? themes[0]
88
+ let appearance = theme?.dataset.appearance ?? 'light'
89
+ const show = () => {
90
+ for (const button of themes) button.setAttribute('aria-checked', String(button === theme))
91
+ for (const button of switches) button.setAttribute('aria-checked', String(button.dataset.setAppearance === appearance))
86
92
  }
87
- example.querySelector('select').addEventListener('change', event => {
93
+ const reload = () => {
88
94
  const url = new URL(frame.src)
89
- url.searchParams.set('theme', event.target.value)
95
+ url.searchParams.set('theme', theme.dataset.theme)
96
+ url.searchParams.set('appearance', appearance === theme.dataset.appearance ? '' : appearance)
90
97
  url.hash = String(index + 1)
91
98
  frame.src = url.toString()
92
- })
99
+ show()
100
+ }
101
+ for (const button of themes) button.addEventListener('click', () => { theme = button; appearance = button.dataset.appearance; reload() })
102
+ for (const button of switches) button.addEventListener('click', () => { appearance = button.dataset.setAppearance; reload() })
103
+ show()
93
104
  window.addEventListener('message', event => {
94
105
  if (event.source !== frame.contentWindow || event.origin !== location.origin || !event.data?.deckStateChanged) return
95
106
  index = event.data.deckStateChanged.index
96
- output.textContent = `Slide ${index + 1}`
97
107
  })
98
108
  }
99
109
 
@@ -38,11 +38,19 @@ function navigation(current) {
38
38
 
39
39
  const exampleFiles = { 'first-talk': resolve(docsRoot, 'examples/first-talk.md'), 'custom-layouts': resolve(projectRoot, 'examples/custom-layouts/slides.md') }
40
40
 
41
- // The theme picker lists every registered theme and starts on the example's own design.
42
- function themeOptions(exampleFile) {
41
+ // The theme picker: every registered theme as a picture of the example's
42
+ // chart slide in it (images/themes/<theme>-thumb.webp, from
43
+ // tools/feature-images.mjs), the example's own theme first and chosen; and light
44
+ // or dark, starting on the theme's own.
45
+ function themePicker(exampleFile) {
43
46
  const current = parseSlides(readFileSync(exampleFile, 'utf8')).deckConfig.theme ?? 'neue'
44
- const themes = loadRegistry(exampleFile).themes
45
- return Object.values(themes).map(theme => `<option value="${theme.id}"${theme.id === current ? ' selected' : ''}>${escape(theme.title)}</option>`).join('')
47
+ const themes = Object.values(loadRegistry(exampleFile).themes).sort((x, y) => (y.id === current) - (x.id === current))
48
+ const buttons = themes.map(theme => {
49
+ const thumb = `images/themes/${theme.id}-thumb.webp`
50
+ const picture = existsSync(resolve(docsRoot, thumb)) ? `<img src="${thumb}" alt="" width="160" height="90" loading="lazy">` : ''
51
+ return `<button type="button" role="radio" aria-checked="${theme.id === current}" data-theme="${theme.id}" data-appearance="${theme.manifest.appearance ?? 'light'}">${picture}<span>${escape(theme.title)}</span></button>`
52
+ }).join('')
53
+ return `<div class="theme-picker" role="radiogroup" aria-label="Theme">${buttons}</div>`
46
54
  }
47
55
 
48
56
  function preview(page) {
@@ -50,8 +58,9 @@ function preview(page) {
50
58
  const custom = page.preview === 'custom-layouts'
51
59
  return `<figure class="slide-example" data-preview>
52
60
  <figcaption><span>${custom ? 'A reusable comparison design' : 'A presentation you can try'}</span><a href="examples/${page.preview}.html?palette=&amp;appearance=" target="_blank" rel="noopener">Open slides</a></figcaption>
53
- <iframe title="${custom ? 'Comparison slide example' : 'Example presentation'}" src="examples/${page.preview}.html?embedded=1&amp;palette=&amp;appearance=" loading="lazy"></iframe>
54
- <div class="preview-controls"><div><button type="button" data-control="prev" aria-label="Previous slide or point">Previous</button><button type="button" data-control="next" aria-label="Next slide or point">Next</button><output aria-live="polite">Slide 1</output></div><label>Look <select aria-label="Example theme">${themeOptions(exampleFiles[page.preview])}</select></label></div>
61
+ <iframe title="${custom ? 'Comparison slide example' : 'Example presentation'}" src="examples/${page.preview}.html?embedded=1&amp;controls=1&amp;draw=1&amp;palette=&amp;appearance=" loading="lazy"></iframe>
62
+ <div class="preview-controls"><span>Move the pointer over the slides for their controls, or use the arrow keys. Press D to draw.</span><div class="appearance-switch" role="radiogroup" aria-label="Light or dark"><button type="button" role="radio" data-set-appearance="light">Light</button><button type="button" role="radio" data-set-appearance="dark">Dark</button></div></div>
63
+ ${themePicker(exampleFiles[page.preview])}
55
64
  </figure>`
56
65
  }
57
66
 
@@ -32,7 +32,7 @@ Drawings live in a file next to your slides: `my-talk.drawings.json` for `my-tal
32
32
  - The first save of a session keeps a copy of the previous file in `.mdeck-backups`, next to your slides.
33
33
  - **Opened from a built folder or a single file**, the page has nowhere to save. Drawings are kept in that browser, and the toolbar's download button saves them as the drawings file, to put next to your slides. Safari may clear such browser data after a week without use, so download it after drawing.
34
34
 
35
- Builds and PDFs include the drawings. To leave them out, add `--no-drawings`:
35
+ Builds and PDFs include the drawings. In the reader view, which a file from `mdeck send` opens in, a **Drawings** button hides and shows them, also for **Save as PDF**; the browser remembers the choice. To leave them out of a build altogether, add `--no-drawings`:
36
36
 
37
37
  ```sh
38
38
  mdeck pdf my-talk.md --no-drawings
@@ -18,7 +18,7 @@ mdeck send my-talk.md
18
18
 
19
19
  Send `my-talk.html` by email. It opens in a **reader view**: an outline of all slides on the left, the slides in the middle, a **Read** mode that stacks every slide for scrolling on a phone, a **Light**/**Dark** button, a **Download PDF** button, and a **Present** button that opens standalone slides. The PDF is inside the file, so there is nothing else to attach.
20
20
 
21
- - Drawings from `my-talk.drawings.json` are part of every build and PDF; `--no-drawings` leaves them out. See [Draw on your slides](drawing.html).
21
+ - Drawings from `my-talk.drawings.json` are part of every build and PDF; `--no-drawings` leaves them out. Readers can hide them with the **Drawings** button in the reader view. See [Draw on your slides](drawing.html).
22
22
  - Your speaker notes are removed. Add `--notes` to keep them in the file, for example when you also present from the same file. Readers only see notes if you put `reader:` with `notes: true` in the settings at the top of your slide file.
23
23
  - Slides with a poll, question, word cloud or scale stay as a record of the talk: the question and its options, marked as answered live. Add `--no-polls` to leave them out of the file and its PDF, together with slides showing the join code (`<qrcode join />`).
24
24
  - The PDF is made while sending, using Chrome or Chromium on your computer. If neither is found, the button offers the browser's own "Save as PDF" dialog instead. Set `MDECK_CHROME` to the browser's path if it is installed somewhere unusual. `--no-pdf` skips it.
@@ -48,6 +48,7 @@ title = "Blue notebook"
48
48
  "--rule" = "#dbe3ee"
49
49
  "--accent" = "#2455c7"
50
50
  "--accent-2" = "#17625c"
51
+ "--accent-3" = "#c99700"
51
52
  "--on-accent" = "#ffffff"
52
53
 
53
54
  [dark]
@@ -59,6 +60,7 @@ title = "Blue notebook"
59
60
  "--rule" = "#24354d"
60
61
  "--accent" = "#7aa2ff"
61
62
  "--accent-2" = "#5ec4b6"
63
+ "--accent-3" = "#f2c94c"
62
64
  "--on-accent" = "#0f1a2b"
63
65
  ```
64
66
 
@@ -76,11 +78,12 @@ The folder must be called `notebook` to match `id`. Use it with `palette: notebo
76
78
  | `--ink-soft` | Body text |
77
79
  | `--muted` | Secondary labels |
78
80
  | `--rule` | Borders |
79
- | `--accent` | Main highlight |
80
- | `--accent-2` | Secondary highlight |
81
+ | `--accent` | Emphasis; notes, tips and important callouts |
82
+ | `--accent-2` | The accent's companion: gradients, a contrast to the accent |
83
+ | `--accent-3` | Attention: warnings and cautions. Use it sparingly, for bars and shapes, never as text: it may be a yellow |
81
84
  | `--on-accent` | Text placed on an accent background |
82
85
 
83
- A theme has no colours of its own: it uses these, and mixes of them, so every palette repaints it. For an inverted slide it uses the palette's other version, as `--inverse-bg`, `--inverse-ink`, `--inverse-accent` and so on:
86
+ A theme has no colours of its own: it uses these, and mixes of them, so every palette repaints it. Give each accent the job its name says: a theme that colours callouts colours note, tip and important with `--accent` and warning and caution with `--accent-3`. For an inverted slide it uses the palette's other version, as `--inverse-bg`, `--inverse-ink`, `--inverse-accent` and so on:
84
87
 
85
88
  ```css
86
89
  .slide--focus {