| name | presentation-factory-html-player |
| description | Assembles the Slalom-branded presenter-mode HTML player into slides/player/. The player is the shippable artifact of a deck. Full presenter mode: current slide + next slide + speaker notes + timer + thumbnail nav + drawing overlay + animation step controls + keyboard shortcuts. Minimalistic chrome, Slalom-branded, single self-contained directory. Reads the slide HTML files and the animations manifest produced by upstream skills.
|
| type | skill |
| plugin | presentation-factory |
| tags | ["type/skill","plugin/presentation-factory","scope/specialty","topic/player"] |
| status | active |
Goal
Wrap a deck's rendered HTML slides in a Slalom-branded player that supports live presentation, recording, and review. Make the player the canonical shippable artifact.
When to invoke
- End of Compose stage, after slides are rendered and animations are annotated
- When the user runs
/presentation-republish (refresh the player without re-rendering slides)
- When upgrading an older deck to the current player version
Required context
references/presentation-factory-foundations.md (Output is HTML-first section, Animations and interactions section).
What the player provides
Audience view (default, fullscreen mode)
What the audience sees on the shared/projected screen:
- Current slide only, full-bleed at the deck's 16:9 aspect ratio
- No chrome unless mouse moves (chrome fades in on motion, fades out after 2 seconds idle)
- Bottom-edge progress bar (subtle, 2px tall, Slalom Blue, fills with deck progress)
- Bottom-right slide counter ("7 / 16") in Avenir Next tabular-nums
- Animation builds run on speaker input
Presenter view (dual-screen, press P to toggle)
What the speaker sees on their laptop (audience sees the audience view on the projector):
- Left panel (60% width): current slide preview at fidelity, with build-step indicator (e.g., "Step 2 of 3")
- Right top (40% width): next slide preview at smaller size
- Right middle: speaker notes for current slide, large Slalom Sans, scroll if long
- Right bottom: presentation timer (mm:ss elapsed), pause button, time-of-day clock
- Top bar: deck title, current slide number / total, jump-to-slide input
Thumbnail nav (press G)
Grid overlay of all slide thumbnails. Click to jump. Press G again or Esc to close. Keyboard-navigable (arrow keys move selection, Enter jumps).
Drawing overlay (press D)
Click+drag draws on the current slide. Useful for live emphasis. Slalom Blue stroke by default; press 1/2/3 to switch to Cyan, Ink, or white. Press E to erase. Press Esc to clear and exit drawing mode. Drawings don't persist across slide changes.
Keyboard shortcuts overlay (press ?)
Modal overlay listing every keyboard shortcut. Press ? again or Esc to close.
Keyboard controls (full list)
| Key | Action |
|---|
| Right arrow, Spacebar, Page Down | Advance one build step. If no more steps, next slide. |
| Left arrow, Shift+Spacebar, Page Up | Back one step. If at first step, previous slide. |
| Home | Jump to first slide |
| End | Jump to last slide |
| F | Toggle fullscreen |
| P | Toggle presenter view (dual-screen) |
| N | Show/hide speaker notes inline (single-screen mode) |
| G | Open/close thumbnail grid |
| D | Toggle drawing overlay |
| R | Replay current slide's animation from step 1 |
| A | Toggle animations off for current slide |
| Shift+A | Toggle animations off for whole deck |
| 1-9 | Drawing colors when drawing mode is active |
| E | Erase tool (drawing mode) |
| Esc | Close overlay / exit fullscreen |
| ? | Show keyboard shortcuts |
Player asset structure
The player ships as a directory under slides/player/:
slides/player/
index.html ← Main entry point. Loads manifest.js, then player.js.
player.css ← All player chrome styling, Slalom-branded.
player.js ← Player logic: navigation, presenter view, drawing, animations.
manifest.js ← Generated. Inlines window.DECK_MANIFEST = {...} for file:// playback.
manifest.json ← Same data as manifest.js, kept for inspection and clean diffs.
The slide HTML files stay in slides/01-*.html, slides/02-*.html, etc. The player loads them as iframes (so each slide's inline CSS and JS stays isolated).
Why manifest.js AND manifest.json: browsers block fetch() from file:// URLs. The .js form sets window.DECK_MANIFEST so the player works when you double-click index.html. The .json form stays for human inspection. Both files contain identical data and must be regenerated together.
About file:// playback: the player itself runs fine from file:// (manifest.js handles that), but slide iframes (../01-title.html) may still be blocked in Chrome's strict mode. The skill also emits a serve.sh helper at the deck root that runs python3 -m http.server and auto-opens the player. Recommend its use for any reviewer who hits a blank slide problem.
What this skill does (workflow)
- Read every
slides/*.html file (exclude player/ if exists).
- For each slide, extract:
- Slide number (from filename
NN-...)
- Slide title (first
<h1> or data-slot="title" content)
- Slide subtitle (if present)
- Speaker notes (from a
<script type="text/x-speaker-notes"> block embedded in each slide HTML, or from a sibling slides/NN-notes.md file)
- Animation step count (from
data-animate="step-N" attribute count)
- Read
slides/animations.manifest.json from presentation-factory-animations and merge.
- Generate
slides/player/manifest.json:
{
"deck_title": "Eliminating Content Bottlenecks",
"brand": "slalom",
"aspect_ratio": "16:9",
"slide_count": 16,
"slides": [
{
"number": 1,
"file": "01-title.html",
"title": "Eliminating content bottlenecks",
"subtitle": "How fashion leaders drive growth with AI and automation",
"notes": "Welcome and introductions. Today we'll cover...",
"transition_in": "fade",
"build_steps": 0
}
]
}
- Copy
resources/player/{index.html,player.css,player.js} from the plugin's resources directory to WIP/[deck]/slides/player/.
- Substitute brand tokens into
player.css from the deck's brand/tokens.css.
- Verify the player loads correctly: open
slides/player/index.html in headless Chromium, confirm manifest loads, confirm slide 1 renders, log to compose.log.
Branding
Player chrome uses Slalom brand by default:
- Background: Slalom Ink
#000A25 for presenter mode
- Chrome accents: Slalom Blue
#0C62FB
- Typography: Avenir Next for chrome labels (slide counter, timer, notes); Lora italic only for the deck title
- Logo: Slalom logo bottom-left of presenter view at 24px tall
For Composable DXP or Client brands, swap tokens accordingly.
Anti-patterns
- Don't add player features beyond the spec. The minimalist promise is the spec.
- Don't load external scripts (no analytics, no Google Fonts CDN, no tracking pixels). The player runs offline.
- Don't auto-advance slides. Speaker controls everything.
- Don't add audience-side reactions or polls. That's a different product surface.
- Don't override slide CSS from the player. The iframe boundary keeps slides isolated.
- Don't ship the player without the keyboard shortcuts overlay. Users will need it.
Output
WIP/[deck]/slides/player/index.html + player.css + player.js + manifest.json
compose.log entry recording player assembly and the verify-pass result