mini-presenter
v0.3.0
Published
Lightweight presentation helper with presenter view, speaker notes, and timer
Maintainers
Readme
mini-presenter
mini-presenter is a tiny local server that injects a display helper into your slides and provides a presenter view with timers, notes, and previews.
This should allow you to present almost any website as a slideshow for as long as it has anchors per slide and build step. With a bit of extra support for special marker hashes you can also have next slide previews.
Features
- Injected display script keeps the presenter view in sync.
- Presenter dashboard with current slide preview, timer, and connection status.
- Optional next-slide preview when your deck exposes slide order.
- Speaker notes via deck API or Markdown files.
- Configurable keyboard shortcuts.
- Optional file watching with auto-reload.
- Optional Cloudflare tunnel via
cloudflaredfor sharing previews.
Installation
# Run directly with npx (no install needed)
npx mini-presenter path/to/deck
# Or install globally
npm install -g mini-presenterQuick start
npx mini-presenter path/to/deck --port 8080 --watch --funnel
npx mini-presenter https://mitsuhiko.github.io/talks/i-was-questioning-life/- Slides:
http://localhost:8080/ - Presenter view:
http://localhost:8080/_/presenter
When you pass a URL, mini-presenter proxies the remote site through the local server. File watching is only available for local folders.
Use --watch to enable file watching and auto-reload on HTML/CSS/JS changes.
Use --funnel to create an anonymous Cloudflare tunnel (requires cloudflared).
Export slides (PDF/PNG)
The exporter will start a dedicated Chrome instance with remote debugging automatically.
npx mini-presenter export ./slides --output slides.pdf
npx mini-presenter export ./slides --output ./images --format png --delay 500Basic requirements for slide decks
Your presentation can be plain HTML/CSS/JS as long as it cooperates with navigation and state reporting:
- Served from a local folder or URL. mini-presenter serves the folder or proxies the URL you pass on the CLI and injects its script into any HTML file.
- Expose a current slide identifier. The injected script uses
window.miniPresenter.getCurrentSlide()if available. Otherwise it falls back tolocation.hash. - React to navigation commands. The presenter sends
next,prev,first,last, andgotoactions. Implement the mini-presenter API (below) or listen for keyboard events (ArrowRight,ArrowLeft,Home,End) and update the slide state yourself. - Update the URL hash. This is the easiest way to keep the presenter preview and notes aligned. When the current slide changes, update
location.hash(or implementgetCurrentSlide()).
If you already have a deck that uses hash-based navigation (Reveal, custom HTML, etc.), it usually “just works.”
Mini-presenter deck API (optional)
Add a global window.miniPresenter object to make the presenter smarter:
window.miniPresenter = {
// Navigation hooks (used instead of keyboard events when present)
next() {},
prev() {},
first() {},
last() {},
goto(hash) {},
// State + metadata
getCurrentSlide() { return location.hash || "#"; },
getSlideList() { return ["#1", "#2", "#3"]; },
getNotes(slideId) { return "Speaker notes"; }
};getSlideList()enables the next-slide preview.getNotes(slideId)provides speaker notes directly from the deck.- If you don’t expose these hooks, the presenter falls back to URL hash updates and keyboard events.
Presenter preview context
The presenter view loads slide previews in iframes with ?_presenter_preview=1.
When that query param is present, the injected script:
- sets
window.miniPresenter.isPresenterPreview = true - sets
document.documentElement.dataset.presenterPreview = "true" - mutes all
<audio>/<video>elements so previews stay silent
Use this flag to disable autoplay audio or heavyweight effects in the presenter view.
Configuration (presenter.json)
Place an optional presenter.json next to your index.html to customize the presenter experience.
{
"title": "My Presentation",
"keyboard": {
"next": ["ArrowRight", "Space", "PageDown", "l", "j"],
"prev": ["ArrowLeft", "PageUp", "h", "k"],
"first": ["Home"],
"last": ["End"]
},
"notes": {
"source": "files"
},
"preview": {
"relativeHash": true
}
}title: Optional presenter title (defaults to the slideshow<title>).keyboard: Custom key bindings for presenter navigation.notes.source:api,files, ornone(default:api+ file fallback).preview.relativeHash: Enable#<hash>~nextpreview resolution.
The config is available at /_/api/config.
Speaker notes
Notes are shown in the presenter view and loaded in this order:
- Presentation API: define
window.miniPresenter.getNotes(slideId)in your deck. - Notes files: add Markdown files under
notes/next to yourindex.html.
File mapping rules:
#1→notes/1.md#1.2→notes/1.2.md(falls back tonotes/1.mdif missing)#2-1→notes/2-1.md(falls back tonotes/2.mdif missing)#/2/1→notes/2--1.md(slashes become--)#intro→notes/intro.md
Numeric hashes with -, ., or -- suffixes fall back to the base number if the
specific file is missing (for example #2/1 → notes/2.md).
Notes are fetched from /_/api/notes?hash=%23intro and rendered as pre-wrapped text.
Next slide preview
The presenter shows a next-slide preview when it can determine the slide order from
window.miniPresenter.getSlideList() in your deck (returns an array of hashes).
If preview.relativeHash is enabled, the preview iframe loads #<hash>~next and expects
slide logic in your deck to resolve it to the next state (including build steps).
AI Use Disclaimer
Note: this library was 100% AI generated with Claude Code. I will try to fix it up as good as possible as I ran into issues, but I cannot vouch for the quality of it.
License and Links
- Issue Tracker
- Discussions
- License: Apache-2.0
