mdeck 3.6.1 → 3.6.2

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 (33) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +44 -2
  3. package/docs/site/assets/site.css +21 -0
  4. package/docs/site/assets/site.js +17 -7
  5. package/docs/site/build.js +15 -6
  6. package/docs/site/content/drawing.md +1 -1
  7. package/docs/site/content/sharing.md +1 -1
  8. package/docs/site/content/welcome.md +70 -6
  9. package/docs/site/examples/chart.svg +5 -0
  10. package/docs/site/examples/contours.jpg +0 -0
  11. package/docs/site/examples/first-talk.drawings.json +1 -0
  12. package/docs/site/examples/first-talk.md +125 -35
  13. package/docs/site/examples/first-talk.results.json +171 -0
  14. package/docs/site/examples/mdeck.svg +66 -0
  15. package/docs/site/examples/references.bib +23 -0
  16. package/docs/site/images/features/citations.webp +0 -0
  17. package/docs/site/images/features/code.webp +0 -0
  18. package/docs/site/images/features/drawing.webp +0 -0
  19. package/docs/site/images/features/polls.webp +0 -0
  20. package/docs/site/images/features/reader.webp +0 -0
  21. package/docs/site/images/features/themes.webp +0 -0
  22. package/docs/site/images/themes/academic-thumb.webp +0 -0
  23. package/docs/site/images/themes/glass-thumb.webp +0 -0
  24. package/docs/site/images/themes/minimal-thumb.webp +0 -0
  25. package/docs/site/images/themes/neue-thumb.webp +0 -0
  26. package/docs/site/images/themes/plain-thumb.webp +0 -0
  27. package/docs/site/images/themes/work-thumb.webp +0 -0
  28. package/package.json +3 -2
  29. package/src/core/labels.js +6 -0
  30. package/src/runtime/ReaderView.jsx +21 -12
  31. package/src/runtime/deck-stage.js +5 -1
  32. package/src/runtime/reader.css +2 -0
  33. package/docs/site/examples/notebook.svg +0 -58
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.6.2 — 2026-10-09
4
+
5
+ - 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.
6
+ - An embedded deck (`?embedded=1`) shows its control bar with `?controls=1`.
7
+ - 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.
8
+
3
9
  ## 3.6.1 — 2026-10-09
4
10
 
5
11
  - `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.
@@ -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.
@@ -6,6 +6,74 @@ The content stays yours. The assistant works from your material, not from its ow
6
6
 
7
7
  [Make your first presentation](getting-started.html)
8
8
 
9
+ ## What you can do with it
10
+
11
+ <div class="features">
12
+
13
+ <section>
14
+
15
+ [![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](images/features/drawing.webp)](images/features/drawing.webp)
16
+
17
+ ### Draw on your slides, from an iPad if you like
18
+
19
+ 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. Drawings are kept with the talk, in its PDF too. [Draw on your slides](drawing.html)
20
+
21
+ </section>
22
+
23
+ <section>
24
+
25
+ [![A poll slide with live bars and a QR code, and a phone showing the same question with one answer picked](images/features/polls.webp)](images/features/polls.webp)
26
+
27
+ ### Ask the room, and show the answers on the slide
28
+
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. The answers are kept with the talk. [Ask your audience](audience.html)
30
+
31
+ </section>
32
+
33
+ <section>
34
+
35
+ [![A slide with author–year citations and the full references at its foot, next to a slide that lists all references](images/features/citations.webp)](images/features/citations.webp)
36
+
37
+ ### Cite your sources properly
38
+
39
+ Cite as in a paper, `[@knuth1984, p. 97]`, from the BibTeX, CSL-JSON or Hayagriva file you already keep. Each slide lists what it cites at its foot, and a references slide lists everything, in APA, Vancouver, Harvard or any CSL style. [Cite a source](more-content.html#cite-a-source)
40
+
41
+ </section>
42
+
43
+ <section>
44
+
45
+ [![The same chart slide in six themes: neue, plain, work, academic, minimal and glass](images/features/themes.webp)](images/features/themes.webp)
46
+
47
+ ### Write the content once, change the look anytime
48
+
49
+ The file holds what you say; a theme and a palette decide how it looks. One line changes every slide, light or dark, and your text never moves. Six themes come with mdeck, more are one command away in the [theme repository](https://gh.tschieber.de/mdeck-themes/). [Change the look](appearance.html)
50
+
51
+ </section>
52
+
53
+ <section>
54
+
55
+ [![A slide with a short Python example and its output below it](images/features/code.webp)](images/features/code.webp)
56
+
57
+ ### Run code on the slide
58
+
59
+ Give a Python or JavaScript example a Run button, change it in front of the audience and run it again. Python runs right in the browser, with packages such as NumPy and pandas, so nothing needs installing. [Show a code example](more-content.html#show-a-code-example)
60
+
61
+ </section>
62
+
63
+ <section>
64
+
65
+ [![The reader view of a talk on a laptop, with an outline of the slides, and on a phone in reading mode](images/features/reader.webp)](images/features/reader.webp)
66
+
67
+ ### Send one file that anyone can open
68
+
69
+ `mdeck send` makes a single HTML file for the people you send the talk to: an outline, a reading mode for phones, light and dark, and the PDF inside. It opens in any browser, also offline, and your speaker notes stay out. [Send, host or print](sharing.html)
70
+
71
+ </section>
72
+
73
+ </div>
74
+
75
+ Also: a presenter view with notes, timer and the next slide; points revealed one at a time; pictures, videos and formulas; a browser editor for those who prefer clicking; and a launch page (`mdeck run`) that gathers it all, with a check for missing pictures and slides that do not fit.
76
+
9
77
  <!-- preview -->
10
78
 
11
79
  ## Bring your material, then shape it
@@ -52,13 +120,9 @@ The `#` makes a heading. The line with `---` starts another slide. A dash before
52
120
  - **Which view or server do I need?** See [Views, commands and servers](views.html).
53
121
  - **Something looks wrong?** Try [the troubleshooting guide](troubleshooting.html).
54
122
 
55
- ## What mdeck does today
56
-
57
- mdeck gives you ready-made slide layouts, different visual themes, speaker notes, pictures, videos, a separate presenter view, drawing on slides (also from an iPad), polls and other questions your audience answers on their phones, and a reader view with a PDF for people you send the slides to. It can also use layouts, themes and colors made for your team, and your assistant can make new ones for a single talk.
58
-
59
- `mdeck run` opens a launch page for your talk with all of this in one place: the presenter view, the editor, these guides, buttons that make the files to share, and a check for missing pictures.
123
+ ## Your file, whoever edits it
60
124
 
61
- Whoever writes the slides, your assistant, a text editor, or the built-in browser editor (`mdeck edit`), the file stays the single copy, and the preview updates as it changes.
125
+ Whoever writes the slides, your assistant, a text editor, or the built-in browser editor (`mdeck edit`), the file stays the single copy, and the preview updates as it changes. mdeck can also use layouts, themes and colours made for your team, and your assistant can make new ones for a single talk.
62
126
 
63
127
  ## Keep this guide nearby
64
128
 
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 820 900"><rect width="820" height="900" fill="var(--surface, #f2f2f2)"/>
2
+ <g stroke="var(--rule, #ddd)" stroke-width="2"><path d="M60 760H780"/><path d="M60 585H780"/><path d="M60 410H780"/><path d="M60 235H780"/></g>
3
+ <rect x="90" y="550" width="70" height="210" rx="5" fill="var(--muted, #888)"/><rect x="205" y="480" width="70" height="280" rx="5" fill="var(--muted, #888)"/><rect x="320" y="130" width="70" height="630" rx="5" fill="var(--accent, #888)"/><rect x="435" y="410" width="70" height="350" rx="5" fill="var(--muted, #888)"/><rect x="550" y="550" width="70" height="210" rx="5" fill="var(--muted, #888)"/><rect x="665" y="620" width="70" height="140" rx="5" fill="var(--muted, #888)"/>
4
+ <g fill="var(--muted, #666)" font-family="sans-serif" font-size="30" text-anchor="middle"><text x="125" y="815">A</text><text x="240" y="815">B</text><text x="355" y="815">C</text><text x="470" y="815">D</text><text x="585" y="815">E</text><text x="700" y="815">F</text></g>
5
+ </svg>
Binary file
@@ -0,0 +1 @@
1
+ {"version":1,"width":1920,"height":1080,"slides":{"draw":[{"id":"peak","tool":"pen","color":"#e11d48","size":7,"points":[[427,193,0.45],[434,188,0.47],[443,182,0.48],[453,178,0.5],[464,175,0.52],[476,174,0.54],[487,173,0.55],[499,175,0.57],[510,177,0.59],[520,181,0.6],[530,186,0.62],[538,191,0.63],[546,197,0.65],[553,203,0.66],[559,209,0.67],[564,216,0.69],[569,223,0.7],[573,230,0.71],[577,237,0.72],[580,245,0.73],[581,252,0.74],[582,261,0.75],[581,269,0.76],[579,277,0.77],[575,285,0.78],[569,293,0.78],[561,299,0.79],[553,305,0.79],[543,309,0.79],[533,312,0.8],[522,314,0.8],[511,315,0.8],[501,315,0.8],[491,315,0.8],[481,314,0.8],[471,312,0.79],[461,311,0.79],[452,309,0.79],[443,307,0.78],[433,304,0.78],[424,301,0.77],[415,297,0.76],[407,292,0.75],[400,286,0.74],[394,280,0.73],[389,272,0.72],[386,265,0.71],[385,257,0.7],[386,249,0.69],[388,241,0.67],[391,233,0.66],[396,226,0.65],[401,220,0.63],[407,214,0.62],[414,209,0.6],[421,204,0.59],[428,199,0.57],[436,194,0.55],[444,190,0.54],[453,186,0.52],[463,182,0.5],[473,179,0.48],[484,177,0.47],[496,176,0.45]]},{"id":"arrow","tool":"pen","color":"#e11d48","size":7,"points":[[600,215,0.45],[615,215,0.49],[630,215,0.53],[644,215,0.57],[659,215,0.61],[674,216,0.64],[689,218,0.67],[704,220,0.7],[719,223,0.73],[733,226,0.75],[748,230,0.77],[763,236,0.79],[778,242,0.79],[793,249,0.8],[807,257,0.8],[822,266,0.79],[837,275,0.79],[852,286,0.77],[867,298,0.75],[881,310,0.73],[896,323,0.7],[911,337,0.67],[926,352,0.64],[941,367,0.61],[956,382,0.57],[970,398,0.53],[985,414,0.49],[1000,430,0.45]]},{"id":"arrow-head","tool":"pen","color":"#e11d48","size":7,"points":[[967,421,0.45],[1000,430,0.8],[993,397,0.45]]}]}}
@@ -1,20 +1,41 @@
1
1
  ---
2
- theme: academic
2
+ theme: neue
3
+ meta:
4
+ title: "A short tour of mdeck"
3
5
  show:
4
6
  organization: none
5
7
  author: none
6
- ---
7
-
8
+ bibliography: references.bib
8
9
  ---
9
10
  :::meta
10
11
  layout: title
12
+ image: ./mdeck.svg
13
+ fit: contain
14
+ alt: "A Markdown file turning into a stack of slides"
11
15
  id: hello
12
16
  :::
13
- # Write it down.
14
- ## Why every lab keeps a notebook
17
+ # Slides from a text file.
18
+ ## A short tour of mdeck
19
+
20
+ :::notes
21
+ These notes are only for you: they show in the presenter view.
22
+ :::
23
+
24
+ ---
25
+ :::meta
26
+ id: how-it-works
27
+ :::
28
+ # From your material to a talk
29
+
30
+ :::steps
31
+ - Hand your slides, papers or notes to an AI assistant
32
+ - It writes one Markdown file with your content
33
+ - mdeck presents it in the browser
34
+ - Send it as one file, or as a PDF
35
+ :::
15
36
 
16
37
  :::notes
17
- Ask who has ever repeated an experiment because nobody remembered how it was done.
38
+ Press Next: the points appear one at a time.
18
39
  :::
19
40
 
20
41
  ---
@@ -22,67 +43,136 @@ Ask who has ever repeated an experiment because nobody remembered how it was don
22
43
  layout: split
23
44
  props:
24
45
  ratio: [1, 1]
46
+ id: write-this
25
47
  :::
26
- # A notebook answers three questions.
48
+ # Write this, get a slide
49
+
50
+ :::slot left
51
+ ```markdown
52
+ # Three ideas
27
53
 
28
- - What did I do?
29
- - What did I see?
30
- - What would I change?
54
+ - Start small
55
+ - Show an example
56
+ - Ask a question
57
+ ```
58
+ :::
31
59
 
32
- :::notes
33
- Everything else in a good notebook serves one of these three questions.
60
+ :::slot right
61
+ A `#` makes the heading, a dash a point, and `---` starts the next slide. Your assistant writes it; you can read and change every word.
34
62
  :::
35
63
 
36
64
  ---
37
65
  :::meta
38
66
  layout: focus
39
- attribution: "A saying in many labs"
40
67
  :::
41
- # If it is not written down, it did not happen.
68
+ # Your talk is a text file: you can read it, change it and keep it.
42
69
 
43
70
  ---
71
+
44
72
  :::meta
45
73
  layout: chapter
46
74
  number: 1
47
- part: "Good habits"
75
+ part: "In the room"
48
76
  :::
49
- # Write as you work.
77
+ # Present it, live.
50
78
 
51
- Two habits that make a notebook useful a year later.
79
+ Draw, ask the room, cite and run code, right on the slides.
52
80
 
53
81
  ---
54
82
  :::meta
55
- layout: split
56
- props:
57
- ratio: [1, 1]
83
+ layout: image-text
84
+ image: ./chart.svg
85
+ alt: "A bar chart with one bar much taller than the others"
86
+ id: draw
58
87
  :::
59
- # Two habits that pay off
88
+ # Draw while you talk
60
89
 
61
- :::slot left
62
- ## Date every page
63
- The date and the experiment's name come first, before anything else.
90
+ - Circle, underline, point with a laser
91
+ - From an iPad paired with the laptop
92
+ - Saved with the slide, also in the PDF
93
+
94
+ :::notes
95
+ The circle and the arrow were drawn on this slide and stay with it.
64
96
  :::
65
97
 
66
- :::slot right
67
- ## Record what went wrong
68
- Failed runs explain the successful ones. Never tear out a page.
98
+ ---
99
+ :::meta
100
+ id: first
101
+ :::
102
+ # Which will you try first?
103
+
104
+ <poll room="first" options="Drawing on slides|Polls from phones|Citations|A new theme" />
105
+
106
+ :::notes
107
+ People answer on their phones and the bars grow live. These answers are kept from a talk.
69
108
  :::
70
109
 
71
110
  ---
72
111
  :::meta
73
- layout: image-text
74
- image: ./notebook.svg
75
- alt: "An open lab notebook with written steps, a results table and a plot"
112
+ id: cite
76
113
  :::
77
- # One page per experiment
114
+ # Cite like in a paper
78
115
 
79
- Aim, materials, steps, results and a short conclusion. Glue in printouts so nothing gets lost.
116
+ Slides are Markdown, a plain text format meant to read well as it is [@gruber2004]. Text and code kept together is an old idea [@knuth1984].
117
+
118
+ Citations use Pandoc's syntax [@pandoc] and any citation style; each slide lists its references at its foot.
119
+
120
+ ---
121
+ :::meta
122
+ id: references
123
+ :::
124
+ # References
125
+
126
+ <bibliography />
127
+
128
+ ---
129
+ :::meta
130
+ id: run
131
+ :::
132
+ # Run code on the slide
133
+
134
+ ```python live editable
135
+ features = ["drawing", "polls", "citations", "themes"]
136
+ print(", ".join(name.capitalize() for name in features))
137
+ ```
138
+
139
+ :::tip
140
+ Press Run, change the list, run it again.
141
+ :::
142
+
143
+ ---
144
+ :::meta
145
+ layout: chapter
146
+ number: 2
147
+ part: "The look"
148
+ :::
149
+ # Content and style, apart.
150
+
151
+ The file holds what you say. A theme decides how it looks.
152
+
153
+ ---
154
+ :::meta
155
+ layout: split
156
+ props:
157
+ ratio: [1, 1]
158
+ :::
159
+ # One file, many looks
160
+
161
+ :::slot left
162
+ ## Themes
163
+ Six come with mdeck, more are one command away. Try the pictures below this deck.
164
+ :::
165
+
166
+ :::slot right
167
+ ## Light or dark
168
+ Every palette has both. Switch while presenting, without touching the file.
169
+ :::
80
170
 
81
171
  ---
82
172
  :::meta
83
173
  layout: full-bleed-image
84
- image: ./notebook.svg
85
- alt: "An open lab notebook with written steps, a results table and a plot"
174
+ image: ./contours.jpg
175
+ alt: "Contour lines like those on a map"
86
176
  overlay: true
87
177
  :::
88
- # Your future self will thank you.
178
+ # Make your next presentation.
@@ -0,0 +1,171 @@
1
+ {
2
+ "version": 1,
3
+ "rooms": {
4
+ "first": {
5
+ "savedAt": "2026-10-09T09:13:00.000Z",
6
+ "closed": false,
7
+ "answers": [
8
+ {
9
+ "phone": 1,
10
+ "at": "2026-10-09T09:12:00.000Z",
11
+ "value": "Drawing on slides"
12
+ },
13
+ {
14
+ "phone": 2,
15
+ "at": "2026-10-09T09:12:01.700Z",
16
+ "value": "A new theme"
17
+ },
18
+ {
19
+ "phone": 3,
20
+ "at": "2026-10-09T09:12:03.400Z",
21
+ "value": "Citations"
22
+ },
23
+ {
24
+ "phone": 4,
25
+ "at": "2026-10-09T09:12:05.100Z",
26
+ "value": "Drawing on slides"
27
+ },
28
+ {
29
+ "phone": 5,
30
+ "at": "2026-10-09T09:12:06.800Z",
31
+ "value": "A new theme"
32
+ },
33
+ {
34
+ "phone": 6,
35
+ "at": "2026-10-09T09:12:08.500Z",
36
+ "value": "Polls from phones"
37
+ },
38
+ {
39
+ "phone": 7,
40
+ "at": "2026-10-09T09:12:10.200Z",
41
+ "value": "Drawing on slides"
42
+ },
43
+ {
44
+ "phone": 8,
45
+ "at": "2026-10-09T09:12:11.900Z",
46
+ "value": "A new theme"
47
+ },
48
+ {
49
+ "phone": 9,
50
+ "at": "2026-10-09T09:12:13.600Z",
51
+ "value": "Polls from phones"
52
+ },
53
+ {
54
+ "phone": 10,
55
+ "at": "2026-10-09T09:12:15.300Z",
56
+ "value": "Drawing on slides"
57
+ },
58
+ {
59
+ "phone": 11,
60
+ "at": "2026-10-09T09:12:17.000Z",
61
+ "value": "A new theme"
62
+ },
63
+ {
64
+ "phone": 12,
65
+ "at": "2026-10-09T09:12:18.700Z",
66
+ "value": "Polls from phones"
67
+ },
68
+ {
69
+ "phone": 13,
70
+ "at": "2026-10-09T09:12:20.400Z",
71
+ "value": "Drawing on slides"
72
+ },
73
+ {
74
+ "phone": 14,
75
+ "at": "2026-10-09T09:12:22.100Z",
76
+ "value": "Citations"
77
+ },
78
+ {
79
+ "phone": 15,
80
+ "at": "2026-10-09T09:12:23.800Z",
81
+ "value": "Polls from phones"
82
+ },
83
+ {
84
+ "phone": 16,
85
+ "at": "2026-10-09T09:12:25.500Z",
86
+ "value": "Drawing on slides"
87
+ },
88
+ {
89
+ "phone": 17,
90
+ "at": "2026-10-09T09:12:27.200Z",
91
+ "value": "Citations"
92
+ },
93
+ {
94
+ "phone": 18,
95
+ "at": "2026-10-09T09:12:28.900Z",
96
+ "value": "Drawing on slides"
97
+ },
98
+ {
99
+ "phone": 19,
100
+ "at": "2026-10-09T09:12:30.600Z",
101
+ "value": "A new theme"
102
+ },
103
+ {
104
+ "phone": 20,
105
+ "at": "2026-10-09T09:12:32.300Z",
106
+ "value": "Polls from phones"
107
+ },
108
+ {
109
+ "phone": 21,
110
+ "at": "2026-10-09T09:12:34.000Z",
111
+ "value": "Drawing on slides"
112
+ },
113
+ {
114
+ "phone": 22,
115
+ "at": "2026-10-09T09:12:35.700Z",
116
+ "value": "A new theme"
117
+ },
118
+ {
119
+ "phone": 23,
120
+ "at": "2026-10-09T09:12:37.400Z",
121
+ "value": "Polls from phones"
122
+ },
123
+ {
124
+ "phone": 24,
125
+ "at": "2026-10-09T09:12:39.100Z",
126
+ "value": "Drawing on slides"
127
+ },
128
+ {
129
+ "phone": 25,
130
+ "at": "2026-10-09T09:12:40.800Z",
131
+ "value": "A new theme"
132
+ },
133
+ {
134
+ "phone": 26,
135
+ "at": "2026-10-09T09:12:42.500Z",
136
+ "value": "Polls from phones"
137
+ },
138
+ {
139
+ "phone": 27,
140
+ "at": "2026-10-09T09:12:44.200Z",
141
+ "value": "Drawing on slides"
142
+ },
143
+ {
144
+ "phone": 28,
145
+ "at": "2026-10-09T09:12:45.900Z",
146
+ "value": "Citations"
147
+ },
148
+ {
149
+ "phone": 29,
150
+ "at": "2026-10-09T09:12:47.600Z",
151
+ "value": "Polls from phones"
152
+ },
153
+ {
154
+ "phone": 30,
155
+ "at": "2026-10-09T09:12:49.300Z",
156
+ "value": "Drawing on slides"
157
+ },
158
+ {
159
+ "phone": 31,
160
+ "at": "2026-10-09T09:12:51.000Z",
161
+ "value": "Citations"
162
+ },
163
+ {
164
+ "phone": 32,
165
+ "at": "2026-10-09T09:12:52.700Z",
166
+ "value": "Polls from phones"
167
+ }
168
+ ]
169
+ }
170
+ }
171
+ }
@@ -0,0 +1,66 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="960" height="800" viewBox="0 0 960 800">
2
+ <!-- A Markdown file becoming a stack of slides. The colours come from the
3
+ deck's palette (var(--accent) …), so the picture follows the theme;
4
+ the fallbacks apply where it is shown as a plain image. -->
5
+ <style>
6
+ .mdk-paper { fill: var(--surface, #eef1fb); stroke: var(--rule, #d5dbf0); stroke-width: 3; }
7
+ .mdk-fold { fill: var(--rule, #d5dbf0); }
8
+ .mdk-shadow { fill: var(--ink, #0c1033); opacity: .12; }
9
+ .mdk-code { font-family: ui-monospace, 'JetBrains Mono', Menlo, Consolas, monospace; font-size: 30px; fill: var(--ink-soft, #2a2f55); }
10
+ .mdk-mark { fill: var(--accent, #1f3fd1); font-weight: 700; }
11
+ .mdk-quiet { fill: var(--muted, #5b6080); }
12
+ .mdk-slide { fill: var(--bg, #ffffff); stroke: var(--ink, #0c1033); stroke-width: 3; }
13
+ .mdk-accent { fill: var(--accent, #1f3fd1); }
14
+ .mdk-accent-2 { fill: var(--accent-2, #e8590c); }
15
+ .mdk-rule { fill: var(--rule, #d5dbf0); }
16
+ .mdk-on-accent { fill: var(--on-accent, #ffffff); font-family: var(--font-display, sans-serif); font-weight: 800; font-size: 96px; }
17
+ .mdk-title { fill: var(--ink, #0c1033); font-family: var(--font-display, sans-serif); font-weight: 800; font-size: 46px; }
18
+ .mdk-arrow { fill: none; stroke: var(--accent-2, #e8590c); stroke-width: 5; stroke-linecap: round; stroke-linejoin: round; }
19
+ </style>
20
+
21
+ <!-- The text file -->
22
+ <g transform="rotate(-5 230 400)">
23
+ <rect class="mdk-shadow" x="56" y="146" width="370" height="510" rx="18"/>
24
+ <path class="mdk-paper" d="M58 130 h290 l62 62 v438 a18 18 0 0 1 -18 18 h-316 a18 18 0 0 1 -18 -18 v-482 a18 18 0 0 1 18 -18 z"/>
25
+ <path class="mdk-fold" d="M348 130 v44 a18 18 0 0 0 18 18 h44 z"/>
26
+ <text class="mdk-code" x="84" y="232"><tspan class="mdk-mark">#</tspan> A talk</text>
27
+ <text class="mdk-code" x="84" y="300"><tspan class="mdk-mark">-</tspan> one idea</text>
28
+ <text class="mdk-code" x="84" y="344"><tspan class="mdk-mark">-</tspan> an example</text>
29
+ <text class="mdk-code mdk-mark" x="84" y="412">---</text>
30
+ <text class="mdk-code mdk-quiet" x="84" y="480">:::notes</text>
31
+ <text class="mdk-code mdk-quiet" x="84" y="524">pause here</text>
32
+ <text class="mdk-code mdk-quiet" x="84" y="568">:::</text>
33
+ </g>
34
+
35
+ <!-- From text to slides -->
36
+ <path class="mdk-arrow" d="M300 112 C 380 20, 560 22, 640 150" stroke-dasharray="2 16"/>
37
+ <path class="mdk-arrow" d="M612 138 l30 16 l4 -34"/>
38
+ <path class="mdk-accent" d="M470 60 l7 17 l17 7 l-17 7 l-7 17 l-7 -17 l-17 -7 l17 -7 z"/>
39
+ <path class="mdk-accent-2" d="M760 210 l5 12 l12 5 l-12 5 l-5 12 l-5 -12 l-12 -5 l12 -5 z"/>
40
+
41
+ <!-- The slides: a chapter, a statement, and a content slide in front -->
42
+ <g transform="rotate(9 680 330)">
43
+ <rect class="mdk-accent-2" x="440" y="200" width="480" height="270" rx="14"/>
44
+ </g>
45
+ <g transform="rotate(3 660 420)">
46
+ <rect class="mdk-accent" x="420" y="290" width="480" height="270" rx="14"/>
47
+ <text class="mdk-on-accent" x="452" y="386">2</text>
48
+ <rect class="mdk-accent-2" x="512" y="350" width="80" height="10" rx="5"/>
49
+ </g>
50
+ <g transform="rotate(-3 640 540)">
51
+ <rect class="mdk-shadow" x="412" y="420" width="480" height="270" rx="14"/>
52
+ <rect class="mdk-slide" x="400" y="404" width="480" height="270" rx="14"/>
53
+ <text class="mdk-title" x="432" y="470">A talk</text>
54
+ <rect class="mdk-accent" x="432" y="486" width="88" height="8" rx="4"/>
55
+ <rect class="mdk-accent" x="432" y="524" width="14" height="14" rx="3"/>
56
+ <rect class="mdk-rule" x="458" y="525" width="150" height="12" rx="6"/>
57
+ <rect class="mdk-accent" x="432" y="562" width="14" height="14" rx="3"/>
58
+ <rect class="mdk-rule" x="458" y="563" width="120" height="12" rx="6"/>
59
+ <rect class="mdk-accent" x="432" y="600" width="14" height="14" rx="3"/>
60
+ <rect class="mdk-rule" x="458" y="601" width="136" height="12" rx="6"/>
61
+ <rect class="mdk-accent-2" x="682" y="580" width="34" height="62" rx="4"/>
62
+ <rect class="mdk-accent" x="726" y="530" width="34" height="112" rx="4"/>
63
+ <rect class="mdk-accent-2" x="770" y="556" width="34" height="86" rx="4"/>
64
+ <rect class="mdk-accent" x="814" y="500" width="34" height="142" rx="4"/>
65
+ </g>
66
+ </svg>
@@ -0,0 +1,23 @@
1
+ @misc{gruber2004,
2
+ author = {Gruber, John},
3
+ title = {Markdown},
4
+ howpublished = {Daring Fireball},
5
+ year = {2004},
6
+ url = {https://daringfireball.net/projects/markdown/}
7
+ }
8
+ @article{knuth1984,
9
+ author = {Knuth, Donald E.},
10
+ title = {Literate Programming},
11
+ journal = {The Computer Journal},
12
+ volume = {27},
13
+ number = {2},
14
+ pages = {97--111},
15
+ year = {1984},
16
+ doi = {10.1093/comjnl/27.2.97}
17
+ }
18
+ @misc{pandoc,
19
+ author = {MacFarlane, John},
20
+ title = {Pandoc: A Universal Document Converter},
21
+ year = {2006},
22
+ url = {https://pandoc.org/}
23
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mdeck",
3
- "version": "3.6.1",
3
+ "version": "3.6.2",
4
4
  "type": "module",
5
5
  "description": "Write slides as a plain text file, present them in the browser, share them as one HTML file or PDF.",
6
6
  "license": "MIT",
@@ -54,7 +54,8 @@
54
54
  "playground": "vite --config playground/vite.config.js",
55
55
  "playground:build": "vite build --config playground/vite.config.js",
56
56
  "readme-images": "node tools/readme-images.mjs",
57
- "theme-images": "node tools/theme-images.mjs"
57
+ "theme-images": "node tools/theme-images.mjs",
58
+ "feature-images": "node tools/feature-images.mjs"
58
59
  },
59
60
  "dependencies": {
60
61
  "@citation-js/core": "^0.9.0",
@@ -13,6 +13,9 @@ export const LABELS = {
13
13
  'reader.appearance': 'Light or dark',
14
14
  'reader.light': 'Light',
15
15
  'reader.dark': 'Dark',
16
+ 'reader.drawings': 'Drawings',
17
+ 'reader.hideDrawings': 'Hide the drawings',
18
+ 'reader.showDrawings': 'Show the drawings',
16
19
  'reader.downloadPdf': 'Download PDF',
17
20
  'reader.savePdf': 'Save as PDF…',
18
21
  'reader.savePdfHint': "Opens the browser's print dialog; choose Save as PDF",
@@ -82,6 +85,9 @@ export const LABELS = {
82
85
  'reader.appearance': 'Hell oder dunkel',
83
86
  'reader.light': 'Hell',
84
87
  'reader.dark': 'Dunkel',
88
+ 'reader.drawings': 'Zeichnungen',
89
+ 'reader.hideDrawings': 'Zeichnungen ausblenden',
90
+ 'reader.showDrawings': 'Zeichnungen einblenden',
85
91
  'reader.downloadPdf': 'PDF herunterladen',
86
92
  'reader.savePdf': 'Als PDF sichern…',
87
93
  'reader.savePdfHint': 'Öffnet den Druckdialog des Browsers; dort „Als PDF sichern“ wählen',
@@ -10,23 +10,25 @@ import './reader.css'
10
10
  import { t, stageLabels } from '../core/labels.js'
11
11
  import { followActiveRooms } from '../live/follow.js'
12
12
  import { Icon } from '../components/Icon.jsx'
13
+ import { currentInk, onInkChange } from './ink/store.js'
13
14
 
14
15
  // The reader view for decks sent around by email: outline, the slides at a
15
- // comfortable size or stacked for reading, light or dark, PDF and a way back
16
- // into the full-screen deck. The theme and palette stay the sender's: a sent
16
+ // comfortable size or stacked for reading, light or dark, the drawings shown
17
+ // or hidden, PDF and a way back into the full-screen deck. The theme and palette stay the sender's: a sent
17
18
  // file carries only those, with their fonts.
18
19
 
19
20
  const lookKey = deckConfig => `mdeck-reader-look:${deckConfig.meta?.title ?? location.pathname}`
20
21
 
21
- // The reader's choice of light or dark, '' for the deck's own.
22
- function restoreAppearance(deckConfig) {
23
- try {
24
- const stored = JSON.parse(localStorage.getItem(lookKey(deckConfig)) ?? 'null')
25
- if (['light', 'dark'].includes(stored?.appearance)) return stored.appearance
26
- } catch {}
27
- return ''
22
+ // The reader's choices: light or dark ('' for the deck's own), and whether
23
+ // the drawings show.
24
+ function restoreLook(deckConfig) {
25
+ let stored = null
26
+ try { stored = JSON.parse(localStorage.getItem(lookKey(deckConfig)) ?? 'null') } catch {}
27
+ return { appearance: ['light', 'dark'].includes(stored?.appearance) ? stored.appearance : '', drawings: stored?.drawings !== false }
28
28
  }
29
29
 
30
+ const hasInk = () => Object.values(currentInk()?.slides ?? {}).some(strokes => strokes?.length)
31
+
30
32
  // Every button carries an icon so the bar reads at a glance.
31
33
  const ReaderIcon = ({ name }) => <Icon name={name} class="reader-icon" />
32
34
 
@@ -57,7 +59,10 @@ function ReadPage({ slide, index, total, deckConfig, width, height, scale, showN
57
59
  export function ReaderView({ deck, deckConfig }) {
58
60
  const { slides } = deck
59
61
  const width = deckConfig.width ?? 1920, height = deckConfig.height ?? 1080
60
- const [appearance, setAppearance] = useState(() => restoreAppearance(deckConfig))
62
+ const [appearance, setAppearance] = useState(() => restoreLook(deckConfig).appearance)
63
+ // Drawings come with the file; a reader may hide them, also for printing.
64
+ const [drawings, setDrawings] = useState(() => restoreLook(deckConfig).drawings)
65
+ const [inked, setInked] = useState(hasInk)
61
66
  const [mode, setMode] = useState('slides')
62
67
  const [index, setIndex] = useState(() => initialIndex(slides))
63
68
  const [navOpen, setNavOpen] = useState(false)
@@ -75,8 +80,11 @@ export function ReaderView({ deck, deckConfig }) {
75
80
 
76
81
  useEffect(() => {
77
82
  loadTheme({ ...deckConfig, appearance: shown }).catch(error => console.warn(error.message))
78
- try { localStorage.setItem(lookKey(deckConfig), JSON.stringify({ appearance })) } catch {}
79
83
  }, [appearance])
84
+ useEffect(() => {
85
+ try { localStorage.setItem(lookKey(deckConfig), JSON.stringify({ appearance, drawings })) } catch {}
86
+ }, [appearance, drawings])
87
+ useEffect(() => onInkChange(() => setInked(hasInk())), [])
80
88
 
81
89
  useEffect(() => {
82
90
  const stage = stageRef.current
@@ -145,7 +153,7 @@ export function ReaderView({ deck, deckConfig }) {
145
153
  const metaLine = [meta.author, meta.organization, meta.date].filter(Boolean).join(' · ')
146
154
  const other = shown === 'dark' ? 'light' : 'dark'
147
155
 
148
- return <div class="reader-view">
156
+ return <div class={`reader-view${drawings ? '' : ' is-ink-hidden'}`}>
149
157
  <header class="reader-top">
150
158
  <button class="reader-btn reader-nav-toggle" onClick={() => setNavOpen(open => !open)} aria-label={t('reader.outline')}><ReaderIcon name="menu" /></button>
151
159
  <h1>{meta.title ?? t('reader.untitled')}</h1>
@@ -155,6 +163,7 @@ export function ReaderView({ deck, deckConfig }) {
155
163
  <button class={`reader-btn${mode === 'slides' ? ' is-active' : ''}`} onClick={() => switchMode('slides')}><ReaderIcon name="slides" />{t('reader.slides')}</button>
156
164
  <button class={`reader-btn${mode === 'read' ? ' is-active' : ''}`} onClick={() => switchMode('read')}><ReaderIcon name="read" />{t('reader.read')}</button>
157
165
  {toggleAllowed && <button class="reader-btn" onClick={() => setAppearance(other === ownAppearance ? '' : other)} title={t('reader.appearance')}><ReaderIcon name={other === 'dark' ? 'moon' : 'sun'} />{t(other === 'dark' ? 'reader.dark' : 'reader.light')}</button>}
166
+ {inked && <button class="reader-btn" onClick={() => setDrawings(on => !on)} aria-pressed={drawings} title={t(drawings ? 'reader.hideDrawings' : 'reader.showDrawings')}><ReaderIcon name={drawings ? 'eye' : 'eye-off'} />{t('reader.drawings')}</button>}
158
167
  {pdf
159
168
  ? <a class="reader-btn" href={pdf} download={`${(meta.title ?? 'slides').replace(/[^\w.-]+/g, '-')}.pdf`}><ReaderIcon name="download" />{t('reader.downloadPdf')}</a>
160
169
  : <button class="reader-btn" onClick={savePdf} title={t('reader.savePdfHint')}><ReaderIcon name="download" />{t('reader.savePdf')}</button>}
@@ -89,7 +89,11 @@ import { inkPaint, snapAngle } from '../core/ink.js'
89
89
  // Zoom: up to this many times the fitted size.
90
90
  const ZOOM_MAX = 6;
91
91
  const VALIDATE_ATTR = 'no_overflowing_text,no_overlapping_text,slide_sized_text';
92
+ // An embedded deck (the presenter view's frames, a preview) hides its
93
+ // controls, unless the page embedding it asks for them with ?controls=1, as
94
+ // the docs' example deck does.
92
95
  const isEmbedded = new URLSearchParams(location.search).has('embedded');
96
+ const hideControls = isEmbedded && !new URLSearchParams(location.search).has('controls');
93
97
 
94
98
  const pad2 = (n) => String(n).padStart(2, '0');
95
99
 
@@ -742,7 +746,7 @@ import { inkPaint, snapAngle } from '../core/ink.js'
742
746
  }
743
747
 
744
748
  _flashOverlay() {
745
- if (!this._overlay || isEmbedded) return;
749
+ if (!this._overlay || hideControls) return;
746
750
  this._overlay.setAttribute('data-visible', '');
747
751
  if (this._hideTimer) clearTimeout(this._hideTimer);
748
752
  this._hideTimer = setTimeout(() => {
@@ -57,3 +57,5 @@
57
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
58
  .reader-entry:hover { opacity: 1; }
59
59
  @media print { .reader-entry { display: none !important; } }
60
+ /* The reader hid the drawings: on the slides, in Read and in print. */
61
+ .reader-view.is-ink-hidden .slide-ink { display: none; }
@@ -1,58 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="1600" height="1000" viewBox="0 0 1600 1000">
2
- <!-- An open lab notebook on a dark desk: ruled pages with entries, a results table, a plot and a pen.
3
- The lower part of the picture stays dark so a heading can sit on it. -->
4
- <defs>
5
- <linearGradient id="desk" x1="0" y1="0" x2="0" y2="1">
6
- <stop offset="0" stop-color="#4a3b30"/>
7
- <stop offset="1" stop-color="#211a15"/>
8
- </linearGradient>
9
- <linearGradient id="spine" x1="0" x2="1">
10
- <stop offset="0" stop-color="#fbf8f1" stop-opacity="0"/>
11
- <stop offset=".5" stop-color="#b9ad98"/>
12
- <stop offset="1" stop-color="#fbf8f1" stop-opacity="0"/>
13
- </linearGradient>
14
- <pattern id="rule" width="10" height="40" patternUnits="userSpaceOnUse">
15
- <line x1="0" y1="39.5" x2="10" y2="39.5" stroke="#bfd3e6" stroke-width="2"/>
16
- </pattern>
17
- </defs>
18
- <rect width="1600" height="1000" fill="url(#desk)"/>
19
- <g transform="translate(260 40) scale(.68) rotate(-3 800 500)">
20
- <rect x="150" y="90" width="1300" height="830" rx="14" fill="#1b2533"/>
21
- <rect x="170" y="105" width="625" height="800" fill="#fbf8f1"/>
22
- <rect x="805" y="105" width="625" height="800" fill="#fbf8f1"/>
23
- <rect x="770" y="105" width="60" height="800" fill="url(#spine)"/>
24
- <rect x="170" y="180" width="625" height="700" fill="url(#rule)"/>
25
- <rect x="805" y="180" width="625" height="700" fill="url(#rule)"/>
26
- <line x1="240" y1="105" x2="240" y2="905" stroke="#e39a9a" stroke-width="3"/>
27
- <line x1="875" y1="105" x2="875" y2="905" stroke="#e39a9a" stroke-width="3"/>
28
- <g fill="none" stroke="#24304a" stroke-width="5" stroke-linecap="round">
29
- <path d="M262 165 h120 M402 165 h70"/>
30
- <path d="M262 245 h300" stroke-width="7"/>
31
- <path d="M262 325 h420 M262 365 h380 M262 405 h300"/>
32
- <path d="M262 485 h30 M310 485 h300 M262 525 h30 M310 525 h340 M262 565 h30 M310 565 h260 M262 605 h30 M310 605 h320"/>
33
- <path d="M262 685 h400 M262 725 h360"/>
34
- <path d="M262 805 h250" stroke="#b5452f"/>
35
- </g>
36
- <path d="M520 798 q20 -18 40 0 t40 0" fill="none" stroke="#b5452f" stroke-width="4" stroke-linecap="round"/>
37
- <g fill="none" stroke="#24304a" stroke-width="4" stroke-linecap="round">
38
- <path d="M897 165 h160"/>
39
- <rect x="897" y="220" width="460" height="200"/>
40
- <path d="M897 270 h460 M897 320 h460 M897 370 h460 M1050 220 v200 M1200 220 v200"/>
41
- <path d="M915 247 h90 M1068 247 h90 M1218 247 h90" stroke-width="5"/>
42
- <path d="M915 297 h60 M1068 297 h70 M1218 297 h50 M915 347 h60 M1068 347 h40 M1218 347 h80 M915 397 h60 M1068 397 h60 M1218 397 h60" stroke-width="3"/>
43
- <path d="M917 820 V480 M917 820 H1360"/>
44
- <path d="M930 800 C1010 790 1060 700 1110 610 S1240 500 1340 495" stroke="#2455c7" stroke-width="5"/>
45
- </g>
46
- <g fill="#2455c7">
47
- <circle cx="960" cy="792" r="9"/><circle cx="1040" cy="742" r="9"/><circle cx="1105" cy="628" r="9"/>
48
- <circle cx="1175" cy="545" r="9"/><circle cx="1260" cy="510" r="9"/><circle cx="1335" cy="500" r="9"/>
49
- </g>
50
- <g transform="rotate(32 1260 690)">
51
- <rect x="1000" y="676" width="430" height="30" rx="14" fill="#1d2735"/>
52
- <rect x="1330" y="676" width="34" height="30" fill="#c9a24b"/>
53
- <path d="M1000 676 l-46 15 l46 15 z" fill="#e8dcc4"/>
54
- <path d="M954 691 l14 -4 v8 z" fill="#1d2735"/>
55
- <rect x="1080" y="668" width="150" height="10" rx="5" fill="#c9a24b"/>
56
- </g>
57
- </g>
58
- </svg>