Community影片與動畫github.com

open-deck-org/opendeck

OpenDeck — build animated, narrated HTML presentation decks with any coding agent. A portable SKILL.md skill + Claude Code plugin marketplace.

相容平台Claude Code~Codex CLI~Cursor
npx add-skill open-deck-org/opendeck

name: opendeck description: Build animated, narrated HTML presentation decks — slides that reveal step-by-step as you click, hover tooltips, a thumbnail rail, fullscreen, and print-to-PDF, plus AI voice narration generated in-browser via ElevenLabs that can be baked into a fully offline file. Use when the user wants a presentation, deck, or slides that animate step-by-step, a self-running or narrated presentation, a deck to present and export, or anything described as "click through and it talks." Can also package the deck as a portable .deck file (deck.json + zipped standalone HTML) for playback in a compatible deck player app. license: MIT metadata: author: Sinisha Djukic version: 1.0.0 created: "2026-06"

OpenDeck — Narrated Animated Presentations

Build an HTML slide deck with in-slide step animations, hover tooltips, a thumbnail rail, fullscreen, print-to-PDF, and AI voice narration — including an in-page "Audio Studio" that generates the voiceover with ElevenLabs and can bake it into a fully offline file.

Use this skill when the user asks for: a presentation/deck/slides that animates step-by-step, a self-running or narrated presentation, a deck they can present and also export, anything describing "click through and it talks," or to package a deck as a portable .deck file for a compatible player app.


What you ship

The shippable kit lives in this skill's assets/ folder. A deck is a single deck file (starter-deck.html, or a .dc.html Design Component when building inside a Claude design project) plus five companion files (all copied into the same folder as the deck), and a zero-dependency bundler for offline export:

FileRoleYou edit it?
deck-stage.jsSlide engine: auto-scaling, keyboard nav, thumbnail rail, speaker notes, print→PDF.No — drop in as-is
deck-enhance.jsdata-step reveals + step dots, data-tip tooltips, Fullscreen (F) & Slides (S) buttons, edit-mode guard.No — drop in as-is
narration-script.jsThe narration text, one line per slide/step.Yes — this is where you write narration
deck-narration.jsNarrate + Auto-play controls (merged into the deck overlay) and the Audio Studio.No — drop in as-is
narration-audio.jsGenerated, not shipped. Holds the audio clips as base64 so narration plays offline. Created by the Studio's "Download audio".No — produced by the user
deck-export.jsIn-browser bundler: deckExport.standalone() inlines every local script, the baked audio, and the web fonts into one self-contained offline .html; deckExport.deck() wraps that as a portable .deck package (deck.json + zip). Plain browser JS — no build step.No — drop in as-is

deck-animation.css is the required CSS for reveals/dots/tooltips — paste it into the deck's <style> (the starter deck already has it inline).


Step 1 — Scaffold the deck

Start from assets/starter-deck.html (in this skill) — it's a complete, working deck you can open directly: 3 slides, a stepped reveal, tooltips, the rail, and the narration controls. Copy it into the project together with the five companion files from assets/ (deck-stage.js, deck-enhance.js, narration-script.js, deck-narration.js, deck-animation.css) — all in one folder, no subdirectories. The engine resolves <script src> and the deck-stage mount relative to the deck, so a flat layout is required. Then replace the demo <section>s with the real slides.

Two wiring shapes — pick the one that matches where you're building

A. Plain HTML (what starter-deck.html uses; also the shape of a "Save as standalone" export). Mount <deck-stage> directly and load the scripts at the end of <body>, stage first:

<deck-stage width="1920" height="1080">
  <section data-label="Title" data-speaker-notes="…">…</section>
  <section data-label="Agenda" data-speaker-notes="…">…</section>
</deck-stage>

<script src="deck-stage.js"></script>
<script src="deck-enhance.js"></script>
<script src="narration-script.js"></script>
<!-- <script src="narration-audio.js"></script>   ← uncomment once baked -->
<script src="deck-narration.js"></script>
<script src="deck-export.js"></script>

B. Design Component (when building inside a Claude design project, authored with dc_write). The deck is a .dc.html; the project's own runtime hydrates it. Mount the stage with <x-import> and load the other scripts from <helmet> in this order:

<x-dc>
<helmet>
  <style> …your slide CSS + the required animation CSS… </style>
  <script src="deck-enhance.js"></script>
  <script src="narration-script.js"></script>
  <!-- <script src="narration-audio.js"></script>   ← uncomment once baked -->
  <script src="deck-narration.js"></script>
  <script src="deck-export.js"></script>
</helmet>

<x-import component-from-global-scope="deck-stage" from="./deck-stage.js"
          width="1920" height="1080" hint-size="100%,100%">
  <section data-label="Title" data-speaker-notes="…">…</section>
  <section data-label="Agenda" data-speaker-notes="…">…</section>
</x-import>
</x-dc>

(Do not hand-write a .dc.html as a raw file and ship it — a DC needs the project's runtime to hydrate. The portable, openable artifact is always the plain-HTML form.)

Rules that matter either way:

  • Slides are the direct <section> children of the stage. Author content at the 1920×1080 design size; the stage scales to fit.
  • Each slide: data-label="…" (rail label) and data-speaker-notes="…" (presenter note, travels with the slide on reorder).
  • Don't set position/inset on <section> — the stage positions them.
  • Keep the deck-stage:not(:defined){visibility:hidden} rule to avoid an unstyled flash.
  • narration-audio.js is commented out until audio is baked.

Step 2 — Add step animations

Give any element a data-step="N" (N = 1, 2, 3…). It starts hidden and reveals when the viewer reaches step N (→ / click / Space). Advancing past the last step moves to the next slide; ← steps back.

<section data-label="Pillars">
  <h2>Three pillars</h2>
  <div class="tile" data-step="1">First</div>
  <div class="tile" data-step="2">Second</div>
  <div class="tile" data-step="3">Third</div>
</section>
  • Step dots are auto-injected (bottom-left) on any slide that has steps — no markup needed.
  • Multiple elements can share the same data-step to reveal together.
  • The required CSS (deck-animation.css) defines what .step-visible / .step-current / .step-past look like. Without it, data-step elements just stay hidden. Paste it into your <style> and restyle freely (it's plain fade-and-rise by default).
  • Elements with no data-step are visible on slide entry (and can stagger in via the optional .frame rule in the CSS).

Tooltips

Add data-tip="explanatory text" to any element for a smart hover tooltip (auto-flips above/below). Tooltips on a not-yet-revealed step stay suppressed until that step shows.


Step 3 — Write the narration

Edit narration-script.js. It exposes window.__NARRATION with a slides[] array — one entry per <section>, in order. Each slide's lines[] maps to steps:

  • lines[0] → spoken when the slide first appears (before any reveal)
  • lines[1] → spoken at data-step="1", lines[2] at data-step="2", …
window.__NARRATION = {
  voiceId: "",                       // optional: pre-fill the Studio's Voice ID
  modelId: "eleven_multilingual_v2",
  gapSeconds: 0.5,                   // beat between a clip ending and auto-advance
  slides: [
    { lines: ["Welcome. Here is the plan."] },                  // slide 1, no steps
    { lines: ["", "First pillar.", "Second.", "The payoff."] }, // slide 2, 3 steps
    { lines: ["Thank you."] }
  ]
};

Writing guidance (matches how the voice reads best):

  • One idea per sentence, short sentences.
  • Spell acronyms with periods so they're read as letters: "A.I.", "U.X.", "G.L.M.".
  • Respell numbers a TTS misreads: "twenty twenty-six" instead of 2026.
  • Empty string "" = silent for that step.

These respellings are spoken-only — they belong ONLY in narration-script.js, never in the slide <section> text. Spoken text and on-screen text are separate sources: the engine reads narration-script.js for the voice and never echoes it on screen. So the slide keeps the human-readable form and the narration line carries the pronunciation hint:

Appears on the slide (<section>)Spoken (narration-script.js)
Our HR team"Our H.R. team"
Shipping in 2026"Shipping in twenty twenty-six"
The AI roadmap"The A.I. roadmap"

If H.R. ever shows up in the slide, the respelling leaked into the markup — fix the <section>, not the narration. (Optional, if you'd rather keep even the narration source clean: ElevenLabs also honors SSML <say-as interpret-as="characters">HR</say-as> and pronunciation dictionaries, but support varies by model — the period-spelling is the simplest reliable default.)

If the user wants you to draft the script, write it from their instructions, keep their tone, and confirm before they generate (generation costs ElevenLabs credits).


Step 4 — Generate the audio (the user does this, in-browser)

Audio is generated in the browser so the ElevenLabs key never leaves the user's machine and is never baked into a file.

While authoring, the control bar shows a blue Studio button (a different colour from the neutral playback controls, so it reads as a build-time tool). It opens a menu of authoring actions:

  • Audio studio — a 5-step wizard: ① Connect (API key + Voice ID) → ② Generate (one clip per step, progress bar) → ③ Download (saves narration-audio.js) → ④ Place (move that file next to the deck) → ⑤ Export (tells the user to ask their AI agent: "Export this presentation as a standalone file."). The export step deliberately promotes only the agent path.
  • Cue overview — a checklist of every step showing which have narration text and which have audio, to spot gaps before exporting.
  • Export standalone HTML — runs the in-browser bundler (Route B) directly, without the console (power-user shortcut).

The Studio button is hidden in the exported standalone (via window.__DECK_EXPORTED). The console API still works too (deckNarration.studio()), printed on every load.

Narration text is edited only in narration-script.js (the file). There is intentionally no in-browser narration editor — browser edits wouldn't survive into the export, so they'd mislead. Change the wording in narration-script.js, then re-generate.

Tell the user to:

  1. Open the deck, click Studio → Audio studio (or run deckNarration.studio() in the dev console).
  2. ① Connect: paste their ElevenLabs API key and Voice ID (ElevenLabs → Voices → the voice → ID), then Next.
  3. ② Generate: click Generate narration — one clip per non-empty line, with a progress bar, cached in the browser (IndexedDB). On success the wizard advances. To re-do lines: edit narration-script.js, reopen, tick Re-generate clips that already exist, and Generate.
  4. ③ Download: click Download audio to save narration-audio.js.
  5. ④ Place: move narration-audio.js into the same folder as the deck.
  6. ⑤ Export: ask the AI agent to "Export this presentation as a standalone file" (the agent runs the Route A build).

Recommend (can't enforce) Chrome or Edge for generation — or serve over http. The clip cache uses IndexedDB, which Chromium allows from file:// but Safari blocks and Firefox treats inconsistently (the ElevenLabs call itself is fine everywhere — it returns Access-Control-Allow-Origin: *). Where the cache is blocked the generated clips live only in memory for that session, so the user must click "Download audio" before reloading or they'll regenerate (and re-pay). The Studio detects this and shows an inline warning; the deck also prints the tip to the console. This only affects generating — once audio is baked, playback reads the inlined map (no IndexedDB), so an exported deck plays offline in every browser, including from a double-clicked file://.

Console API (all available globally once the deck loads):

  • deckNarration.studio() — open the generation panel
  • deckNarration.play() / .stop() — start/stop auto-play
  • deckNarration.narration(true|false) — narration on/off
  • deckNarration.status(){clips, narrationOn, autoplay}

Step 5 — Playback model (what the two on-screen controls do)

The deck overlay (bottom bar, alongside Prev/Next/Reset/Fullscreen/Slides) gains two controls:

  • Narrate — master on/off. When on, every step you land on speaks, whether you click through manually, use arrows, click a tile, or jump via a thumbnail.
  • Auto-play / Pause — hands-free: reveals a step, plays its clip, and advances when the clip ends. Turning it on implies Narrate on. Pausing keeps Narrate on, so you can keep clicking through and still hear each step.

It is a single-driver model: any step change cancels stale audio and any pending auto-advance, then plays the clip for the step you're actually on — so manual clicks during auto-play never double-advance or desync.


Step 6 — Bake audio in & export offline

To make narration play on any machine, offline, with no key:

  1. In the Studio, click Download audio → produces narration-audio.js (all clips as base64 data URLs).
  2. Move that file into the deck's folder (next to deck-stage.js etc.).
  3. Bundle everything into one self-contained .html. There are two routes — both produce the same single file with zero tools to install:

Route A — ask the skill to build it (recommended; no server, no Python)

The user just says "build the standalone" and you (the agent) do the bundling with your own file tools — nothing runs on their machine. Procedure:

  1. Read the deck .html and inline every local <script src="…"> (the kit scripts + narration-audio.js if present) by replacing each tag with <script>…file contents…</script>. Skip absolute/CDN URLs.
    • Mark it exported: always inject <script>window.__DECK_EXPORTED=true;</script> before the (inlined) deck-narration.js. This hides the authoring Studio button in the export, and — when no audio was baked — the Narrate + Auto-play buttons too (nothing to play). With baked audio, Narrate/Auto-play stay; the Studio button is always gone.
    • Activate baked audio: if narration-audio.js is in the folder, also un-comment its <script> line so the inlined copy is live. If it's absent, just bundle without it (the __DECK_EXPORTED flag handles hiding the now-useless controls).
    • ⚠ Neutralize closing tags: the engine files contain the literal text </script> inside their doc-comments. Before inlining a file, replace </script with <\/script in its contents, or the first one will close the block early and corrupt the file.
  2. Fonts — ask the user which they want (use AskUserQuestion):
    • Bake the fonts in (recommended) — fetch each woff2 the deck's Google-Fonts <link> references and inline it as a base64 @font-face rule, then remove the <link>/preconnect. Result renders identically offline.
    • Keep the CDN <link> — smaller file, but the custom fonts only render with an internet connection; offline (or if the fonts aren't installed locally) it falls back to system fonts and looks different.
  3. Write the result as your-deck.standalone.html. Expect it to be large (the audio dominates — often several MB).

Route B — self-service in the browser (deck-export.js)

For a user who'd rather not involve the agent: load deck-export.js (it's one of the kit files) and run this in the browser dev console:

deckExport.standalone()      // downloads your-deck.standalone.html

It does the same inlining (scripts + baked audio + fonts, with the </script> fix) entirely in the browser. It auto-detects narration-audio.js and always tries to bake fonts (falling back to the CDN link if offline). Because it fetch()es same-origin files, the deck must be served over http(s) for this route — not opened via file://. Inside a Claude design project, the project's own "Save as standalone HTML" export is the equivalent.

Without baking audio, the export is a clean silent deck: the Narrate + Auto-play controls are hidden (nothing to play), while all the animation, tooltips, and navigation still work. The clips live only in the browser that made them, so voice returns only if someone regenerates with a key (the deckNarration.studio() console API stays available for that).

Route C — package as a portable .deck file

When the user wants to present on a phone/tablet, or asks to "export as a .deck", package the deck for a compatible deck player app. A .deck is just a Zip whose root holds:

my-talk.deck
├── deck.json     # the manifest — see assets/deck.schema.json
└── index.html    # the standalone export (Route A/B output) — fully self-contained

Two routes, same output:

deckExport.deck()   // browser console: builds the standalone, writes deck.json, downloads my-talk.deck

…or ask the agent to do it with file tools: produce the standalone HTML exactly as in Route A, write it as index.html, add a deck.json, and zip the two at the archive root as <id>.deck. Either way the result is one portable file.

The deck.json manifest is defined by assets/deck.schema.json (JSON Schema) — read that file for the exact field set, types, patterns, and defaults. In short: only entry ("index.html") is strictly required; id (the filename), title, orientation (landscape for these 1920×1080 decks), and optional author/version round it out. Add "$schema": "./deck.schema.json" to a hand-written deck.json for editor validation.

Because the deck is the standalone bundle, baked audio plays offline inside the player too — it renders in a real web origin, so all animation, narration, fonts, and tooltips work exactly as in the browser.


Gotchas / notes

  • CSS is mandatory. If reveals don't show, the animation CSS isn't in the deck's <style>. Copy from deck-animation.css.
  • Load order & location. The four scripts go inside <helmet> in the given order, and all files must sit in the same folder as the deck (the engine resolves src paths relative to the deck — a subfolder breaks the helmet scripts). For a plain (non-DC) HTML deck, put the scripts at the end of <body> instead, with deck-stage.js first.
  • Design tokens. The CSS ships with plain values; if the deck has a design system, swap them for its tokens (easing, accent color) so reveals/dots/tooltips match.
  • Slide count. slides[] in the script should line up with the <section>s; extra/short entries just stay silent — fine while drafting.
  • Don't rename the globals. deck-enhance.js/deck-narration.js look for window.__NARRATION and window.__NARRATION_AUDIO; keep those names.
  • Editing safety. deck-enhance.js suppresses nav keys while you're typing in a field/contenteditable, so authoring text won't flip slides.

相關技能