mdeck 3.10.1 → 3.12.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.
- package/CHANGELOG.md +44 -0
- package/README.md +15 -4
- package/assets/base.css +6 -1
- package/docs/reference/authoring.md +111 -9
- package/docs/site/assets/site.css +2 -0
- package/docs/site/content/appearance.md +1 -1
- package/docs/site/content/audience.md +36 -8
- package/docs/site/content/commands.md +6 -3
- package/docs/site/content/drawing.md +5 -5
- package/docs/site/content/getting-started.md +2 -32
- package/docs/site/content/install.md +49 -0
- package/docs/site/content/media.md +20 -1
- package/docs/site/content/presenting.md +67 -9
- package/docs/site/content/sharing.md +1 -0
- package/docs/site/content/welcome.md +21 -1
- package/docs/site/content/writing.md +27 -0
- package/docs/site/images/features/hotspots.webp +0 -0
- package/docs/site/images/features/languages.webp +0 -0
- package/docs/site/pages.js +1 -0
- package/examples/custom-layouts/slides.md +21 -9
- package/examples/showcase/refs.bib +21 -0
- package/examples/showcase/slides.ar.drawings.json +11 -0
- package/examples/showcase/slides.ar.md +567 -0
- package/examples/showcase/slides.de.drawings.json +11 -0
- package/examples/showcase/slides.de.md +567 -0
- package/examples/showcase/slides.de.results.json +57 -0
- package/examples/showcase/slides.en.drawings.json +11 -0
- package/examples/showcase/{slides.md → slides.en.md} +112 -5
- package/package.json +1 -1
- package/skills/write-slides/SKILL.md +14 -2
- package/src/build/check.js +34 -7
- package/src/build/components.js +5 -3
- package/src/build/config.js +2 -0
- package/src/build/homePlugin.js +64 -14
- package/src/build/include.js +249 -0
- package/src/build/inkPlugin.js +12 -2
- package/src/build/relayAccess.js +3 -1
- package/src/build/renderCheck.js +23 -3
- package/src/build/slidesPlugin.js +70 -22
- package/src/build/svgCheck.js +65 -0
- package/src/build/timingPlugin.js +96 -0
- package/src/cli/args.js +6 -5
- package/src/cli/index.js +77 -21
- package/src/components/Bibliography.jsx +7 -2
- package/src/components/Poll.jsx +2 -2
- package/src/components/QrCodeTag.jsx +2 -2
- package/src/components/VideoPlayer.jsx +6 -1
- package/src/components/WordCloud.jsx +7 -6
- package/src/components/activity.jsx +11 -8
- package/src/components/poll.css +2 -0
- package/src/core/editDeck.js +44 -7
- package/src/core/icons.js +9 -1
- package/src/core/labels.js +78 -0
- package/src/core/parseSlides.js +16 -6
- package/src/core/reactions.js +27 -0
- package/src/core/resultsReport.js +153 -0
- package/src/core/source.js +8 -1
- package/src/core/timing.js +83 -0
- package/src/core/validateDeck.js +11 -2
- package/src/editor/components/DeckSettings.jsx +1 -0
- package/src/editor/components/MetaFields.jsx +1 -0
- package/src/home/Home.jsx +39 -93
- package/src/home/home.css +5 -17
- package/src/layouts/layoutApi.jsx +51 -8
- package/src/layouts/renderSlide.jsx +13 -1
- package/src/live/answer/answer.css +9 -0
- package/src/live/answer/answer.js +32 -4
- package/src/live/client.js +23 -4
- package/src/live/rooms.js +18 -6
- package/src/runtime/KeyHelp.jsx +115 -0
- package/src/runtime/PresenterChrome.jsx +168 -0
- package/src/runtime/Reactions.jsx +56 -0
- package/src/runtime/RehearsalSummary.jsx +129 -0
- package/src/runtime/component.js +27 -0
- package/src/runtime/deck-stage.js +371 -37
- package/src/runtime/helpKeys.js +75 -0
- package/src/runtime/ink/bus.js +13 -5
- package/src/runtime/ink/outbox.js +27 -0
- package/src/runtime/ink/room.js +37 -12
- package/src/runtime/ink/segments.js +21 -0
- package/src/runtime/main.jsx +508 -286
- package/src/runtime/shared.js +77 -0
- package/src/runtime/slideControls.js +60 -0
- package/src/runtime/steps.js +36 -0
- package/src/runtime/useTalkTimer.js +67 -0
- package/examples/citations/refs.bib +0 -30
- package/examples/citations/refs.yml +0 -21
- package/examples/citations/slides.md +0 -43
- package/examples/citations/slides.pdf +0 -0
- package/examples/poll/slides.md +0 -127
- package/examples/showcase/slides.drawings.json +0 -11
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// State that deck components share across slides and across the windows of
|
|
2
|
+
// one talk: the presenter's slide frame, the audience window, other deck
|
|
3
|
+
// windows in this browser, and a paired iPad. Components reach it through
|
|
4
|
+
// `mdeck/component` (component.js). Values travel on the ink bus (ink/bus.js)
|
|
5
|
+
// as `shared` messages; nothing is saved, and a window that opens later asks
|
|
6
|
+
// the others for what they hold (`shared-ask`). A deck whose components use
|
|
7
|
+
// none of it sends nothing.
|
|
8
|
+
//
|
|
9
|
+
// The latest change wins as it arrives. A value replayed for a window that
|
|
10
|
+
// opened later only fills a name that window does not know yet, so no clocks
|
|
11
|
+
// are compared between devices.
|
|
12
|
+
|
|
13
|
+
const values = new Map()
|
|
14
|
+
const listeners = new Map()
|
|
15
|
+
let send = null
|
|
16
|
+
let asked = false
|
|
17
|
+
let view = 'deck'
|
|
18
|
+
|
|
19
|
+
// Windows that only show the talk read shared state but do not change it.
|
|
20
|
+
const READ_ONLY = new Set(['preview', 'reader', 'follow'])
|
|
21
|
+
|
|
22
|
+
/** The kind of window this is: presenter, audience, deck, follow, preview or reader. */
|
|
23
|
+
export function setView(next) { view = next }
|
|
24
|
+
export function currentView() { return view }
|
|
25
|
+
|
|
26
|
+
function notify(name) {
|
|
27
|
+
for (const listener of listeners.get(name) ?? []) listener(values.get(name))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Asks the others once, when this window has both a bus and a component
|
|
31
|
+
// that uses shared state.
|
|
32
|
+
function ask() {
|
|
33
|
+
if (asked || !send || !listeners.size) return
|
|
34
|
+
asked = true
|
|
35
|
+
send({ type: 'shared-ask' })
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The bus this window sends on. */
|
|
39
|
+
export function connectShared(sender) {
|
|
40
|
+
send = sender
|
|
41
|
+
ask()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** A `shared` or `shared-ask` message from another window. */
|
|
45
|
+
export function receiveShared(message) {
|
|
46
|
+
if (message.type === 'shared-ask') {
|
|
47
|
+
for (const [name, value] of values) send?.({ type: 'shared', name, value, replay: true })
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
if (typeof message.name !== 'string') return
|
|
51
|
+
if (message.replay && values.has(message.name)) return
|
|
52
|
+
values.set(message.name, message.value)
|
|
53
|
+
notify(message.name)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function getShared(name) { return values.get(name) }
|
|
57
|
+
|
|
58
|
+
/** Sets a value here and in every other window; ignored where the talk is only shown. */
|
|
59
|
+
export function setShared(name, value) {
|
|
60
|
+
if (READ_ONLY.has(view)) return false
|
|
61
|
+
// Only what survives JSON reaches the other windows; keep this one the same.
|
|
62
|
+
const plain = value === undefined ? null : JSON.parse(JSON.stringify(value))
|
|
63
|
+
values.set(name, plain)
|
|
64
|
+
notify(name)
|
|
65
|
+
send?.({ type: 'shared', name, value: plain })
|
|
66
|
+
return true
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function subscribeShared(name, listener) {
|
|
70
|
+
if (!listeners.has(name)) listeners.set(name, new Set())
|
|
71
|
+
listeners.get(name).add(listener)
|
|
72
|
+
ask()
|
|
73
|
+
return () => listeners.get(name)?.delete(listener)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** For tests: forget everything. */
|
|
77
|
+
export function resetShared() { values.clear(); listeners.clear(); send = null; asked = false; view = 'deck' }
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// What the presenter can do on the slide shown, for the phone clicker
|
|
2
|
+
// (main.jsx): the buttons components mark with `data-presenter-control` (a
|
|
3
|
+
// poll's lock, eye, tick and Reset), the slide's videos (play or pause), and
|
|
4
|
+
// its hotspots (zoom to a part, back to the whole slide). The clicker's slide
|
|
5
|
+
// frame reports them; a tap on the clicker presses the original here, so the
|
|
6
|
+
// change reaches the other screens the way a click on the slide does.
|
|
7
|
+
|
|
8
|
+
const activeSlide = stage => stage.querySelector(':scope > [data-deck-active]')
|
|
9
|
+
|
|
10
|
+
function controlElements(stage) {
|
|
11
|
+
const slide = activeSlide(stage)
|
|
12
|
+
if (!slide) return []
|
|
13
|
+
return [...slide.querySelectorAll('[data-presenter-control], video')].filter(element => !element.closest('[data-deck-static]'))
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const labelOf = element => element.getAttribute('data-presenter-control') || element.getAttribute('aria-label') || element.getAttribute('title') || element.textContent.trim()
|
|
17
|
+
|
|
18
|
+
/** { items: [{ label, icon, pressed, disabled }], hotspots: n, hotspot } for the slide shown. `icon` names one of mdeck's icons (`data-presenter-icon`). */
|
|
19
|
+
export function slideControls(stage) {
|
|
20
|
+
const items = controlElements(stage).map(element => element.localName === 'video'
|
|
21
|
+
? { label: element.paused ? 'Play the video' : 'Pause the video', icon: element.paused ? 'play' : 'pause', pressed: !element.paused, disabled: false }
|
|
22
|
+
: { label: labelOf(element), icon: element.getAttribute('data-presenter-icon') || null, pressed: element.getAttribute('aria-pressed') === 'true', disabled: !!element.disabled })
|
|
23
|
+
const hotspots = stage.hotspotMode === 'off' ? 0 : Math.min(9, stage.hotspots.length)
|
|
24
|
+
return { items, hotspots, hotspot: hotspots ? stage.hotspot ?? null : null }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Presses control `index` of slideControls(stage). */
|
|
28
|
+
export function pressSlideControl(stage, index) {
|
|
29
|
+
const element = controlElements(stage)[index]
|
|
30
|
+
if (!element || element.disabled) return false
|
|
31
|
+
if (element.localName === 'video') {
|
|
32
|
+
// Pressed from the toolbar or a phone, not in this frame: the browser may
|
|
33
|
+
// refuse sound here. The audience window plays it; this copy goes silent.
|
|
34
|
+
if (element.paused) element.play().catch(error => {
|
|
35
|
+
if (error?.name !== 'NotAllowedError') return
|
|
36
|
+
element.muted = true
|
|
37
|
+
return element.play()
|
|
38
|
+
}).catch(() => {})
|
|
39
|
+
else element.pause()
|
|
40
|
+
} else element.click()
|
|
41
|
+
return true
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Calls `report(slideControls(stage))` whenever they may have changed, a few times a second at most. */
|
|
45
|
+
export function watchSlideControls(stage, report) {
|
|
46
|
+
// A timer, not an animation frame: a presenter view in a background tab
|
|
47
|
+
// gets no frames, but its clicker still wants to know.
|
|
48
|
+
let timer = 0
|
|
49
|
+
const send = () => {
|
|
50
|
+
if (timer) return
|
|
51
|
+
timer = setTimeout(() => { timer = 0; report(slideControls(stage)) }, 60)
|
|
52
|
+
}
|
|
53
|
+
for (const type of ['slidechange', 'statechange', 'zoomchange', 'hotspotschange']) stage.addEventListener(type, send)
|
|
54
|
+
// Videos say when they play or pause; their events do not bubble.
|
|
55
|
+
for (const type of ['play', 'pause']) stage.addEventListener(type, send, true)
|
|
56
|
+
const observer = new MutationObserver(send)
|
|
57
|
+
observer.observe(stage, { subtree: true, childList: true, attributes: true, attributeFilter: ['aria-pressed', 'disabled', 'data-deck-active', 'data-presenter-control', 'data-presenter-icon'] })
|
|
58
|
+
send()
|
|
59
|
+
return () => observer.disconnect()
|
|
60
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// The steps of a slide: what appears one press at a time.
|
|
2
|
+
//
|
|
3
|
+
// Steps follow the slide's document order. Inside an SVG drawing that order
|
|
4
|
+
// is also what is drawn on top, so a drawing may number its parts instead:
|
|
5
|
+
// `data-step="2"` shows after `data-step="1"`, and parts with the same number
|
|
6
|
+
// appear together. A drawing's numbered steps take the place of its first
|
|
7
|
+
// one; parts with a bare `data-step` keep their own place.
|
|
8
|
+
|
|
9
|
+
// [[element, …], …]: each step's elements, in the order they are shown.
|
|
10
|
+
export function slideSteps(slide) {
|
|
11
|
+
if (!slide) return []
|
|
12
|
+
const order = []
|
|
13
|
+
const drawings = new Map()
|
|
14
|
+
for (const element of slide.querySelectorAll('[data-step]')) {
|
|
15
|
+
const drawing = element.closest('svg')
|
|
16
|
+
const number = numbered(element.getAttribute('data-step'))
|
|
17
|
+
if (!drawing || number == null) { order.push([element]); continue }
|
|
18
|
+
let parts = drawings.get(drawing)
|
|
19
|
+
if (!parts) {
|
|
20
|
+
parts = new Map()
|
|
21
|
+
drawings.set(drawing, parts)
|
|
22
|
+
order.push(parts)
|
|
23
|
+
}
|
|
24
|
+
if (!parts.has(number)) parts.set(number, [])
|
|
25
|
+
parts.get(number).push(element)
|
|
26
|
+
}
|
|
27
|
+
return order.flatMap(entry => entry instanceof Map
|
|
28
|
+
? [...entry.keys()].sort((a, b) => a - b).map(number => entry.get(number))
|
|
29
|
+
: [entry])
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function numbered(value) {
|
|
33
|
+
if (value == null || !value.trim()) return null
|
|
34
|
+
const number = Number(value)
|
|
35
|
+
return Number.isFinite(number) ? number : null
|
|
36
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// The presenter view's clock: the time of the whole talk and of each slide.
|
|
2
|
+
// It reads the wall clock rather than counting ticks, so it does not drift,
|
|
3
|
+
// and keeps its state in sessionStorage, so a reload carries on where it was.
|
|
4
|
+
// Time counts only while the clock runs; it goes to the slide shown.
|
|
5
|
+
//
|
|
6
|
+
// A rehearsal is the same clock, started from nothing: the time each slide
|
|
7
|
+
// got is what the rehearsal summary offers to save (core/timing.js).
|
|
8
|
+
import { useEffect, useLayoutEffect, useRef, useState } from 'preact/hooks'
|
|
9
|
+
|
|
10
|
+
const fresh = () => ({ running: false, rehearsing: false, mark: 0, total: 0, spent: {}, slide: null })
|
|
11
|
+
|
|
12
|
+
function load(key) {
|
|
13
|
+
try {
|
|
14
|
+
const saved = JSON.parse(sessionStorage.getItem(key))
|
|
15
|
+
if (saved && typeof saved.total === 'number' && saved.spent && typeof saved.spent === 'object') return { ...fresh(), ...saved }
|
|
16
|
+
} catch {}
|
|
17
|
+
return fresh()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// The time since the last mark goes to the whole talk and the slide shown.
|
|
21
|
+
function settle(state, now) {
|
|
22
|
+
if (!state.running || state.slide == null) return { ...state, mark: now }
|
|
23
|
+
const gone = Math.max(0, now - state.mark)
|
|
24
|
+
return { ...state, mark: now, total: state.total + gone, spent: { ...state.spent, [state.slide]: (state.spent[state.slide] ?? 0) + gone } }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function useTalkTimer(key, slideId, { rehearse = false } = {}) {
|
|
28
|
+
const [state, setState] = useState(() => {
|
|
29
|
+
const saved = load(key)
|
|
30
|
+
// ?rehearse=1 starts a rehearsal, unless this page is in one already.
|
|
31
|
+
if (rehearse && !saved.rehearsing) return { ...fresh(), rehearsing: true, running: true, mark: Date.now(), slide: slideId }
|
|
32
|
+
return saved
|
|
33
|
+
})
|
|
34
|
+
const [, setTick] = useState(0)
|
|
35
|
+
const stateRef = useRef(state)
|
|
36
|
+
stateRef.current = state
|
|
37
|
+
const update = change => setState(current => change(settle(current, Date.now())))
|
|
38
|
+
|
|
39
|
+
useEffect(() => { try { sessionStorage.setItem(key, JSON.stringify(state)) } catch {} }, [state])
|
|
40
|
+
// At once, not after the next paint: in a background tab that comes late,
|
|
41
|
+
// and the time in between went to the slide before.
|
|
42
|
+
useLayoutEffect(() => { if (slideId != null && slideId !== state.slide) update(s => ({ ...s, slide: slideId })) }, [slideId])
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (!state.running) return
|
|
45
|
+
const timer = setInterval(() => setTick(n => n + 1), 500)
|
|
46
|
+
return () => clearInterval(timer)
|
|
47
|
+
}, [state.running])
|
|
48
|
+
|
|
49
|
+
const live = state.running ? Math.max(0, Date.now() - state.mark) : 0
|
|
50
|
+
const seconds = ms => Math.floor(ms / 1000)
|
|
51
|
+
return {
|
|
52
|
+
running: state.running,
|
|
53
|
+
rehearsing: state.rehearsing,
|
|
54
|
+
totalElapsed: seconds(state.total + live),
|
|
55
|
+
slideElapsed: seconds((state.spent[state.slide] ?? 0) + live),
|
|
56
|
+
// Each slide's time so far, in seconds.
|
|
57
|
+
spent: () => {
|
|
58
|
+
const now = settle(stateRef.current, Date.now())
|
|
59
|
+
return Object.fromEntries(Object.entries(now.spent).map(([id, ms]) => [id, ms / 1000]))
|
|
60
|
+
},
|
|
61
|
+
toggle: () => update(s => ({ ...s, running: !s.running })),
|
|
62
|
+
pause: () => update(s => ({ ...s, running: false })),
|
|
63
|
+
reset: () => setState(s => ({ ...fresh(), slide: s.slide })),
|
|
64
|
+
startRehearsal: () => setState(s => ({ ...fresh(), rehearsing: true, running: true, mark: Date.now(), slide: s.slide })),
|
|
65
|
+
endRehearsal: () => setState(s => ({ ...fresh(), slide: s.slide })),
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
@article{sweller1988,
|
|
2
|
-
author = {Sweller, John},
|
|
3
|
-
title = {Cognitive Load During Problem Solving: Effects on Learning},
|
|
4
|
-
journal = {Cognitive Science},
|
|
5
|
-
year = {1988},
|
|
6
|
-
volume = {12},
|
|
7
|
-
number = {2},
|
|
8
|
-
pages = {257--285},
|
|
9
|
-
doi = {10.1207/s15516709cog1202_4}
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
@article{roediger2006,
|
|
13
|
-
author = {Roediger, Henry L. and Karpicke, Jeffrey D.},
|
|
14
|
-
title = {Test-Enhanced Learning: Taking Memory Tests Improves Long-Term Retention},
|
|
15
|
-
journal = {Psychological Science},
|
|
16
|
-
year = {2006},
|
|
17
|
-
volume = {17},
|
|
18
|
-
number = {3},
|
|
19
|
-
pages = {249--255},
|
|
20
|
-
doi = {10.1111/j.1467-9280.2006.01693.x}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
@book{mayer2009,
|
|
24
|
-
author = {Mayer, Richard E.},
|
|
25
|
-
title = {Multimedia Learning},
|
|
26
|
-
edition = {2},
|
|
27
|
-
publisher = {Cambridge University Press},
|
|
28
|
-
address = {Cambridge},
|
|
29
|
-
year = {2009}
|
|
30
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# The same deck can draw on a Typst (Hayagriva) file as well.
|
|
2
|
-
freeman2014:
|
|
3
|
-
type: Article
|
|
4
|
-
title: Active learning increases student performance in science, engineering, and mathematics
|
|
5
|
-
author:
|
|
6
|
-
- Freeman, Scott
|
|
7
|
-
- Eddy, Sarah L.
|
|
8
|
-
- McDonough, Miles
|
|
9
|
-
- Smith, Michelle K.
|
|
10
|
-
- Okoroafor, Nnadozie
|
|
11
|
-
- Jordt, Hannah
|
|
12
|
-
- Wenderoth, Mary Pat
|
|
13
|
-
date: 2014
|
|
14
|
-
page-range: 8410-8415
|
|
15
|
-
serial-number:
|
|
16
|
-
doi: 10.1073/pnas.1319030111
|
|
17
|
-
parent:
|
|
18
|
-
type: Periodical
|
|
19
|
-
title: Proceedings of the National Academy of Sciences
|
|
20
|
-
volume: 111
|
|
21
|
-
issue: 23
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
theme: academic
|
|
3
|
-
meta:
|
|
4
|
-
title: "What helps people learn"
|
|
5
|
-
author: "Tilman Schieber"
|
|
6
|
-
organization: "FHNW"
|
|
7
|
-
bibliography: [refs.bib, refs.yml]
|
|
8
|
-
csl: apa
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
:::meta
|
|
13
|
-
layout: title
|
|
14
|
-
:::
|
|
15
|
-
# What helps people learn
|
|
16
|
-
## Citations from a reference file
|
|
17
|
-
|
|
18
|
-
:::notes
|
|
19
|
-
The references come from `refs.bib` (BibTeX, as Zotero exports it) and `refs.yml` (Hayagriva, Typst's format). Try `csl: vancouver` or `lang: de` in the deck settings.
|
|
20
|
-
:::
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
# Less on the slide
|
|
24
|
-
|
|
25
|
-
Working memory holds only a few things at once, so a slide that makes people search wastes it [@sweller1988].
|
|
26
|
-
|
|
27
|
-
Words spoken alongside a picture beat the same words printed on it [@mayer2009, chap. 11].
|
|
28
|
-
|
|
29
|
-
---
|
|
30
|
-
# Ask, don't tell
|
|
31
|
-
|
|
32
|
-
As @roediger2006 showed, recalling something fixes it better than reading it again.
|
|
33
|
-
|
|
34
|
-
Lectures with active learning cut failure rates by about a third [@freeman2014; see also @roediger2006].
|
|
35
|
-
|
|
36
|
-
:::notes
|
|
37
|
-
Freeman et al. pooled 225 studies [-@freeman2014].
|
|
38
|
-
:::
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
# References
|
|
42
|
-
|
|
43
|
-
<bibliography />
|
|
Binary file
|
package/examples/poll/slides.md
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
theme: neue
|
|
3
|
-
meta:
|
|
4
|
-
title: "Ask the room"
|
|
5
|
-
author: "Tilman Schieber"
|
|
6
|
-
organization: "FHNW"
|
|
7
|
-
date: "2026-09-30"
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
:::meta
|
|
12
|
-
layout: title
|
|
13
|
-
:::
|
|
14
|
-
# Ask the room.
|
|
15
|
-
## Live questions in a plain slide file.
|
|
16
|
-
|
|
17
|
-
:::notes
|
|
18
|
-
Start this deck with `mdeck run slides.md --network` so phones in the same network can answer.
|
|
19
|
-
|
|
20
|
-
The deck shows every kind of question mdeck has: a poll, a scale, a word cloud and open answers. The QR code appears once, on the next slide.
|
|
21
|
-
:::
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
# Take out your phone.
|
|
25
|
-
|
|
26
|
-
<qrcode join />
|
|
27
|
-
|
|
28
|
-
:::notes
|
|
29
|
-
Leave this up until most people have scanned. The code is the same for the whole talk: phones that joined now follow along, and each question appears on them when its slide comes up.
|
|
30
|
-
|
|
31
|
-
Come back to this slide for latecomers, or leave the join code on the last question too.
|
|
32
|
-
:::
|
|
33
|
-
|
|
34
|
-
---
|
|
35
|
-
:::meta
|
|
36
|
-
title: "Poll: how do you make slides?"
|
|
37
|
-
:::
|
|
38
|
-
# How do you make slides today?
|
|
39
|
-
|
|
40
|
-
<poll room="tools" options="PowerPoint|Keynote|Google Slides|LaTeX Beamer|Markdown" qr="false" />
|
|
41
|
-
|
|
42
|
-
:::notes
|
|
43
|
-
A poll: one button per answer. Give people half a minute and read out the leader once the bars settle.
|
|
44
|
-
|
|
45
|
-
Anyone can change their answer: only the latest vote from each phone counts.
|
|
46
|
-
|
|
47
|
-
Before the talk, hover over the results and click Reset to clear test votes.
|
|
48
|
-
:::
|
|
49
|
-
|
|
50
|
-
---
|
|
51
|
-
:::meta
|
|
52
|
-
title: "Scale: Markdown"
|
|
53
|
-
:::
|
|
54
|
-
# How well do you know Markdown?
|
|
55
|
-
|
|
56
|
-
<scale room="markdown" min="1" max="5" low="Never used it" high="Every day" qr="false" />
|
|
57
|
-
|
|
58
|
-
:::notes
|
|
59
|
-
A scale: phones show the numbers from 1 to 5, the slide counts each one and shows the average.
|
|
60
|
-
|
|
61
|
-
A high average means the rest of the talk can go faster.
|
|
62
|
-
:::
|
|
63
|
-
|
|
64
|
-
---
|
|
65
|
-
:::meta
|
|
66
|
-
title: "Word cloud: slides"
|
|
67
|
-
:::
|
|
68
|
-
# One word for your last presentation?
|
|
69
|
-
|
|
70
|
-
<wordcloud room="mood" placeholder="One word" qr="false" />
|
|
71
|
-
|
|
72
|
-
:::notes
|
|
73
|
-
A word cloud: people may send as many words as they like. The more often a word comes in, the larger it gets, packed around the middle; capitals do not matter.
|
|
74
|
-
|
|
75
|
-
Pick out the largest word and one surprising small one.
|
|
76
|
-
:::
|
|
77
|
-
|
|
78
|
-
---
|
|
79
|
-
:::meta
|
|
80
|
-
title: "Open question"
|
|
81
|
-
:::
|
|
82
|
-
# What would you like to ask?
|
|
83
|
-
|
|
84
|
-
<question room="ask" placeholder="Your question" qr="false" />
|
|
85
|
-
|
|
86
|
-
:::notes
|
|
87
|
-
Open answers: the newest eight appear as cards, and people can send several.
|
|
88
|
-
|
|
89
|
-
Answer one or two now, and promise to go through the rest afterwards; the room keeps them until you reset it.
|
|
90
|
-
:::
|
|
91
|
-
|
|
92
|
-
---
|
|
93
|
-
# Each question is one line.
|
|
94
|
-
|
|
95
|
-
```markdown
|
|
96
|
-
<qrcode join />
|
|
97
|
-
<poll room="tools" options="PowerPoint|Keynote|Markdown" />
|
|
98
|
-
<scale room="markdown" min="1" max="5" low="Never" high="Daily" />
|
|
99
|
-
<wordcloud room="mood" />
|
|
100
|
-
<question room="ask" placeholder="Your question" />
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
- **room** — a short name, different for every question in the deck.
|
|
104
|
-
- **qr="false"** — leave out the code when `<qrcode join />` showed it earlier.
|
|
105
|
-
- **question** — optional; without it, phones show the slide heading.
|
|
106
|
-
|
|
107
|
-
:::notes
|
|
108
|
-
Point at the lines that made the previous slides. That is all the author writes.
|
|
109
|
-
|
|
110
|
-
The QR code takes the slide's colours, so it fits every theme; phone cameras read light-on-dark codes too.
|
|
111
|
-
:::
|
|
112
|
-
|
|
113
|
-
---
|
|
114
|
-
# Try it on your own computer.
|
|
115
|
-
|
|
116
|
-
```sh
|
|
117
|
-
mdeck run slides.md --network
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
- **Same network** — phones must be able to reach your computer.
|
|
121
|
-
- **Lecture hall** — run `mdeck server` on a web server; the slides stay on your laptop.
|
|
122
|
-
|
|
123
|
-
:::notes
|
|
124
|
-
For a lecture hall, run `mdeck server` on a web server once and point the deck's `server` setting at it. The phones only talk to that server; the slides never leave this laptop. The "Ask your audience" guide has the details.
|
|
125
|
-
|
|
126
|
-
Many university networks keep devices apart. If phones cannot open the page, use a phone hotspot or a server on the internet.
|
|
127
|
-
:::
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 1,
|
|
3
|
-
"width": 1920,
|
|
4
|
-
"height": 1080,
|
|
5
|
-
"slides": {
|
|
6
|
-
"draw-on-any-slide": [
|
|
7
|
-
{"id":"tour:underline","tool":"pen","color":"#e11d48","size":7,"points":[[120,462,0.45],[135,464,0.48],[150,466,0.52],[165,467,0.55],[180,468,0.58],[195,469,0.61],[210,469,0.64],[225,468,0.66],[240,467,0.68],[255,465,0.7],[270,464,0.72],[285,462,0.73],[300,460,0.74],[315,459,0.75],[330,458,0.75],[345,458,0.75],[360,459,0.74],[375,460,0.73],[390,461,0.72],[405,463,0.71],[420,465,0.69],[435,467,0.67],[450,469,0.64],[465,470,0.62],[480,471,0.59],[495,472,0.56],[510,472,0.53],[525,471,0.49],[540,470,0.46],[555,468,0.43],[570,467,0.39],[585,465,0.36],[600,464,0.33],[615,462,0.3],[630,462,0.27],[645,461,0.25],[660,462,0.22],[675,462,0.2],[690,464,0.19],[705,466,0.17],[720,468,0.16]]},
|
|
8
|
-
{"id":"tour:circle","tool":"pen","color":"#e11d48","size":6,"points":[[226,491,0.5],[230,491,0.54],[235,491,0.57],[239,492,0.6],[243,494,0.64],[247,497,0.66],[250,500,0.69],[252,504,0.71],[254,508,0.73],[255,512,0.74],[256,516,0.75],[255,520,0.75],[254,524,0.75],[252,528,0.74],[250,532,0.73],[247,534,0.71],[244,537,0.69],[240,539,0.66],[237,541,0.63],[233,542,0.6],[229,543,0.57],[225,543,0.54],[221,543,0.5],[217,543,0.46],[213,541,0.43],[209,540,0.4],[206,538,0.36],[203,535,0.34],[200,531,0.31],[198,528,0.29],[196,523,0.27],[196,519,0.26],[196,515,0.25],[197,510,0.25],[199,506,0.25],[201,503,0.26],[204,500,0.27],[207,497,0.29],[211,495,0.31],[215,494,0.34],[219,493,0.37],[223,493,0.4],[227,493,0.43],[231,493,0.47],[235,495,0.5],[239,496,0.54]]}
|
|
9
|
-
]
|
|
10
|
-
}
|
|
11
|
-
}
|