htmlpptskill
Health Warn
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 9 GitHub stars
Code Fail
- fs module — File system access in .github/workflows/ci.yml
- rm -rf — Recursive force deletion command in install.sh
Permissions Pass
- Permissions — No dangerous permissions requested
No AI report is available for this listing yet.
一句話讓 AI 做出 reveal.js 簡報:可離線播放、自動驗 QR,附講義 PPTX/PDF。Claude Code、Codex CLI、Gemini CLI、Grok Build 皆可安裝的 Agent Skill
htmlpptskill
跟 AI 說一句「做一份 HTML 簡報」,拿到能離線播、QR 驗過、觀眾能用手機投票、還附講義 PPTX/PDF 的成品。
▶ 打開 Live Demo,直接操作
這份 12 頁的 Demo 就是用這個 skill 做的:逐步出現、Auto-Animate、3D 翻卡、頁內測驗、手機投票(模擬 60 人)、滑桿對比。279 KB 單檔,電腦按 → 翻頁。
reveal.js 簡報製作流程,包成兩個 Agent Skill。
Claude Code・Codex CLI・Gemini CLI・Grok Build 都能裝。
安裝後的 skill 內建作者的固定規則(references/fixed-rules.md)與規劃設計原則(references/design-principles.md);X 上分享的簡報技巧整理見 docs/html-slides-tips-from-x-2026-10-17.md。
45 秒看完流程:一句話、逐步出現、Auto-Animate、翻卡、測驗、手機投票、QA、三種交付。
另一份實際講座成品:封面 GSAP 逐字浮現、七張卡片一次翻面、頁內真的能作答的測驗、網站+repo 雙 QR。54 頁、3 MB、拔掉網路照樣播。
安裝 · 10 分鐘上手 · 現場互動 · 快捷鍵、換場與動畫技巧 · 疑難排解
為什麼不用 PowerPoint 就好?
| 你遇過的事 | 這個 repo 的做法 |
|---|---|
| 會場沒網路,簡報裡的字型、圖、外掛全部掛掉 | 交付前 QA_OFFLINE=1 跑一次:任何對外請求都算失敗,reveal.js、字型、圖片、QR 全內嵌成單一 HTML |
| QR 印錯,台下掃到 404 | QA 會把每張 QR 逐張截圖解碼,跟你寫的網址比對,不符就 exit 1 |
| AI 產的投影片文字溢出、少講者備註,播到一半才發現 | 逐頁截圖拼成一張 montage.png,溢出、JS 錯誤、缺備註一次列出 |
| 主辦單位要 PPTX,你只有 HTML | render-slides → build-handout:一頁一圖的 PPTX,講者備註進備忘稿;PDF 一行指令 |
| 想要「炫一點」但怕翻車 | 效果 skill 分穩重/中間/驚豔三檔,內建減少動態效果與 QA 靜態模式 |
| 講者在 PPTX 上手改了字,改動回不去 HTML | 附完整實例:從 PPTX 抽文字、解 QR 圖、回灌 HTML |
| 想讓台下一起作答,又不想把觀眾導去別的平台 | 內附手機即時投票:掃 QR 作答、投影片即時統計、按 A 揭曉同步到手機;學會演講現場 60 人使用、150 支手機壓測,斷網照樣能揭曉 |
兩個 skill
| Skill | 一句話 | 什麼時候會被觸發 |
|---|---|---|
html-slide-deck |
從大綱到交付的主流程:版型、圖片、QR、互動頁、觀眾手機即時投票、逐頁 QA、講義 PPTX/PDF、原生可編輯 PPTX | 「做一份 HTML 簡報」「reveal.js」「簡報加截圖/QR」「現場投票」「轉 PPT/講義」「離線簡報」 |
html-slide-effects |
加在其上的效果:逐步出現、Auto-Animate、3D 翻卡、GSAP 逐字、計時條、點圖放大、手機捲動 | 「加動畫」「卡片翻面」「讓簡報更生動」「有什麼效果可以加」 |
30 秒安裝
git clone https://github.com/keanu77/htmlpptskill.git && cd htmlpptskill
./install.sh # 偵測到哪個 CLI 就裝哪個;既有同名 skill 先搬到 skills-backup/
| 你的 CLI | 裝到哪 | 之後怎麼叫 |
|---|---|---|
| Claude Code | ~/.claude/skills/ |
輸入 / 看到 /html-slide-deck,或直接說「做一份 HTML 簡報」。也可當 plugin:/plugin marketplace add keanu77/htmlpptskill → /plugin install htmlpptskill@keanu77 |
| Codex CLI | ~/.codex/skills/ |
直接描述任務,或 $html-slide-deck |
| Gemini CLI | ~/.gemini/skills/ |
gemini skills list 確認;或 gemini skills link <repo>/skills/html-slide-deck |
| Grok Build | ~/.grok/skills/ |
直接描述任務(它也會讀 ~/.claude/skills/) |
只裝一家:./install.sh codex。跟專案一起 commit:./install.sh --project(裝到 .agents/skills/,三家都掃)。
npm i -D playwright && npx playwright install chromium # 截圖 QA、講義渲染、PDF;Linux 無 GUI 再加 install-deps
npm i -g pptxgenjs # 圖片型講義與原生 PPTX
python3 -m pip install -r requirements.txt # segno、pillow、python-pptx(建議 venv)
腳本從執行目錄往上找 node_modules,找不到再找 npm 全域;也可設 SKILL_PKG_ROOT。
| 平台 | 支援 |
|---|---|
| macOS | 全功能(pptx-to-pdf.sh 需要 Microsoft PowerPoint) |
| Linux | 全功能,除了 PowerPoint 原生匯出(PDF 改用 print-pdf.mjs);無 GUI 主機要裝中文字型(Noto Sans CJK) |
| Windows | 腳本用 node 直接跑;install.sh 用 WSL 或手動複製 skills\*;python3 通常是 python(設 PYTHON=python) |
10 分鐘看到第一份成品
mkdir my-deck && cd my-deck && npm init -y
npm i -D playwright && npx playwright install chromium && npm i reveal.js@5 pptxgenjs
SKILL=~/.claude/skills/html-slide-deck # 換成你的 CLI 的路徑
cp $SKILL/references/reveal-template.html draft.html # 9 頁範本,〈…〉與 example.com 都是占位
node $SKILL/scripts/inline-assets.mjs draft.html index.html # 內嵌 reveal.js 與圖片 → 離線單檔
QA_OFFLINE=1 node $SKILL/scripts/qa-screenshots.mjs index.html qa/ # 逐頁截圖、QR、溢出 → 看 qa/montage.png
node $SKILL/scripts/render-slides.mjs index.html render/
node $SKILL/scripts/build-handout.mjs render/ handout.pptx "講者" # 圖片型講義(備註進備忘稿)
node $SKILL/scripts/print-pdf.mjs index.html handout.pdf # PDF 講義
或者什麼都不打,直接跟 agent 說:
用 html-slide-deck 做一份 20 頁的「XXX」簡報,聽眾是住院醫師,會場沒網路,每個工具頁放 QR。
它會先給你大綱表等你確認,再產 HTML、跑 QA、交付三種檔案。
流程長什麼樣
問三件事(觀眾/有沒有網路/要不要特效)→ 大綱表(等你確認)
├─ A 單檔手寫(≤25 頁) references/reveal-template.html → inline-assets.mjs
└─ B 內容/版型分離(>25 頁) references/offline-build-template/:content.js 一頁一個物件 → npm run all
圖片:AI 底圖(任何生圖工具)、Playwright 真實截圖、segno QR(每張 .qrcard 帶 data-url)
QA:qa-screenshots.mjs ─ 截圖、溢出、JS 錯誤、對外請求、QR 逐張解碼比對、montage.png ─ 有問題 exit 1
交付:index.html(單檔)+ 講義 PPTX + PDF(+ 原生可編輯 PPTX)
效果:html-slide-effects → 加完回頭重跑 QA
互動:手機投票 poll-slides.mjs(講者版另存)、互動地圖、頁內測驗
做法 B 的範本裡有什麼
content.js:8 頁中性起手,一頁一個物件(type決定版型、notes是備註)。50 頁完整實例在examples/workshop-2026/,複製過來就能 build。build.js:25 種版型的渲染函式(封面、章節、三卡、流程、階梯、prompt、平台優缺點、比較表、決策樹、工具頁+大 QR、資源頁…)。scripts/check.mjs:頁數對 content.js、溢出、JS 錯誤、對外請求、QR 逐張解碼。PLAN.md+ROLE-AUTHOR.md/ROLE-REVIEWER.md:想讓兩個 agent 一個主筆一個稽核,角色由你指定。
📱 現場互動:手機投票、互動地圖
左:投影片即時顯示全場作答比例,講者按 A 揭曉;右:觀眾手機同步顯示答案。2026-10 學會演講現場 60 人同時使用。
觀眾用手機即時作答
頁內測驗只有台上一個人點;要全場一起作答,用 references/live-poll/。
| 你要的 | 做法 |
|---|---|
| 觀眾不用裝 App、不用登入 | 掃 QR 開網頁作答,匿名、可改答案、不重複計票 |
| 投影片上即時看到全場答案 | 選擇題顯示百分比,猜數字顯示直方圖與中位數,加入頁顯示在線手機數 |
| 揭曉時台下也看得到 | 講者按 A,投影片標出正解與說明,答案同步推到每支手機;揭曉前手機拿不到答案 |
| 彩排完要清掉測試票 | 在該題按 Shift+R |
| 會場網路斷了 | 投影片顯示提示並改輪詢;A 照樣揭曉 |
| 人多會不會塞車 | 現場 60 人順利使用;上台前 150 支模擬手機壓測,150 票約 0.2–1.2 秒寫入並推到投影片 |
後端是 Cloudflare Worker+Durable Object(免費方案即可),部署一次之後只改題目檔。
<section data-poll-join></section> <!-- 加入頁:大 QR+在線手機數 -->
<section data-poll="warmup"></section> <!-- 一題一頁,題目定義在 worker/src/polls.json -->
cp -R $SKILL/references/live-poll/worker my-poll
(cd my-poll && npm install && npx wrangler login && npx wrangler secret put ADMIN_KEY && npx wrangler deploy) # 同一把 key 也寫進 my-poll/.dev.vars
node $SKILL/scripts/poll-slides.mjs index.html index-live.html --worker my-poll --url https://slide-poll.<子網域>.workers.dev
產出的 index-live.html 內含講者控制用的 ADMIN_KEY,只放講者電腦;要分享的版本用原本的 index.html,詳見 SECURITY.md。
要公開展示(像 Live Demo)就用模擬模式:不連後端、不含 key,進到投票頁自動跑出作答過程,A 一樣能揭曉。
node $SKILL/scripts/poll-slides.mjs index.html demo.html --polls polls.json --sim 60
互動地圖與製作技巧頁
- 互動地圖:點場館顯示項目、最近據點與直線距離,按鈕切換「20 km 圈」「標出 20 km 外」;經緯度投影的 SVG,離線可用。
- 製作技巧頁:Q&A 前一頁放 PowerPoint/PDF/HTML 優缺點比較表與 AI 協作清單。
- PPTX 轉 HTML 插入互動頁:以「原 PPTX 第幾頁之後」指定位置,插頁後自動重編頁碼。
程式碼與內容範例:references/interactive-pages.md。
🎬 快捷鍵、換場與動畫技巧
以下是作者 55 頁工作坊簡報實際用過、驗證過的做法,適用 reveal.js 5.2。點開每一項看可直接複製的程式碼。同一份內容也在 docs/TECHNIQUES.md。
⌨️ 播放快捷鍵
reveal.js 內建
| 鍵 | 功能 |
|---|---|
→ ↓ Space N |
下一步(下一個 fragment 或下一頁) |
← ↑ P |
上一步 |
S |
開講者視窗:目前頁、下一頁、備註、計時 |
Esc 或 O |
總覽(所有頁縮圖,點一下跳過去) |
F |
全螢幕 |
B 或 . |
黑屏(再按一次回來;問答時很好用) |
G |
跳到指定頁碼 |
Home End |
第一頁、最後一頁 |
Alt+點擊 |
放大點擊的位置(zoom 外掛,預設未載) |
網址參數:?print-pdf 進列印模式(瀏覽器另存 PDF)、?view=scroll 手機捲動閱讀、#/10 直接開第 11 頁(hash 從 0 起算)。
自己加的
| 鍵 | 功能 | 程式碼 |
|---|---|---|
X |
聚光燈開關,滾輪調大小 | 4.1 |
T |
計時條重新計時 | 4.2 |
| 點截圖 | 放大,Esc 關閉 |
4.3 |
自訂鍵位前先確認沒撞到內建:B、S、F、G、O、N、P、H/J/K/L(方向)已被佔用。在輸入框裡按鍵要 e.stopPropagation(),否則 reveal 會把數字當成翻頁。
🔀 換場
建議:一般頁淡入淡出,章節頁才有動作。
每一頁都滑動(slide)看久了會累,也讓「換段落」失去標記。作者最後用的配置:
| 頁面 | 換場 |
|---|---|
| 一般內容頁 | fade+fast(約 0.4 秒) |
| 章節頁 | zoom |
| 兩頁要「長出來」 | Auto-Animate(見 3.2) |
Reveal.initialize({
width: 1280, height: 720,
transition: 'fade', // 全場預設
transitionSpeed: 'fast', // default | fast | slow
backgroundTransition: 'fade',
// ...
});
<!-- 章節頁單獨指定 -->
<section class="chapter" data-transition="zoom"> … </section>
全部換場選項
| 屬性 | 值 |
|---|---|
transition(全場)或 data-transition(單頁) |
none、fade、slide、convex、concave、zoom |
| 進場與離場分開 | data-transition="zoom-in fade-out"、"slide-in fade-out" |
| 速度 | transitionSpeed 或 data-transition-speed:default、fast、slow |
| 背景 | backgroundTransition 或 data-background-transition,選項同上 |
建議:全場只用一種基本換場,特殊換場留給章節頁與一兩個關鍵時刻。
✨ 動畫技巧
3.1 逐步出現(fragment)按一下出一項,講的時候聽眾不會先讀完整頁。零成本,最該先用。
<ul>
<li class="fragment">第一點</li>
<li class="fragment fade-up">第二點(往上浮現)</li>
<li class="fragment highlight-current-blue">講到這點時變色</li>
</ul>
<div class="fragment" data-fragment-index="2">指定出現順序</div>
卡片與箭頭一起出現(流程圖):卡片和它前面的箭頭共用同一個 index。
<div class="card fragment fade-up" data-fragment-index="1">教學想法</div>
<div class="arrow fragment" data-fragment-index="2">→</div>
<div class="card fragment fade-up" data-fragment-index="2">Prompt</div>
決策樹一列一列出現:同一列的問題、「是 →」、結果共用 index,「↓ 否」用下一個 index。
效果名稱:fade-up、fade-down、fade-left、fade-right、fade-in-then-out、grow、shrink、strike、highlight-red、highlight-current-blue 等。
兩頁都加 data-auto-animate,相同 data-id 的元素會自動從前一頁的位置、大小、顏色補間到後一頁。適合「總覽放大成單項」「一張卡長出一整排」。
<section data-auto-animate>
<h2>四階梯</h2>
<div class="rung" data-id="lv0" style="width:560px;margin:40px auto 0">Lv0 Claude Artifacts</div>
</section>
<section data-auto-animate>
<h2>四階梯</h2>
<div class="ladder">
<div class="rung" data-id="lv0" style="margin-top:132px">Lv0 Claude Artifacts</div>
<div class="rung" style="margin-top:88px">Lv1 GitHub</div>
<div class="rung" style="margin-top:44px">Lv2 Cloudflare</div>
<div class="rung">Lv3 Zeabur</div>
</div>
</section>
Reveal.initialize({ autoAnimateDuration: 0.9, autoAnimateEasing: 'cubic-bezier(.4,.2,.2,1)' });
可以補間的:位置、尺寸、字級、顏色、背景、padding、margin。沒有 data-id 時,相同文字或相同 src 的元素也會自動配對。
按一下,一組卡片依序翻面(正面是分類,背面是工具名稱);點單張可以翻回。
<div class="grid flipall fragment custom">
<div class="flip"><div class="inner">
<div class="front card">① 影像判讀</div>
<div class="back card">影像學習站<br>肩部影像課程</div>
</div></div>
<!-- 其他卡片 -->
</div>
.reveal .fragment.custom.flipall { opacity: 1; visibility: inherit; } /* 關鍵:沒有這行,正面在點擊前是隱形的 */
.flip { perspective: 1100px; height: 196px; cursor: pointer; }
.flip .inner { position: relative; width: 100%; height: 100%;
transition: transform .9s cubic-bezier(.4,.2,.2,1); transform-style: preserve-3d; }
.flip .front, .flip .back { position: absolute; inset: 0; backface-visibility: hidden; -webkit-backface-visibility: hidden; }
.flip .back { transform: rotateY(180deg); }
.flipall.visible .flip:not(.off) .inner, .flip.on .inner { transform: rotateY(180deg); }
.flip:nth-child(2) .inner { transition-delay: .07s } /* 以此類推,做出波浪 */
.flip:nth-child(3) .inner { transition-delay: .14s }
document.querySelectorAll('.flip').forEach(c => c.addEventListener('click', () => {
c.parentNode.classList.contains('visible') ? c.classList.toggle('off') : c.classList.toggle('on');
}));
原理:fragment 顯示時 reveal 會替元素加上 visible class,CSS 靠它觸發翻面。
十幾張小卡從深處旋轉飛到定位,逐一錯開。純 CSS,不增加檔案大小。
<div class="wall fragment custom">
<div class="root">sportsmedicine.tw</div>
<div class="subs"><span>imaging</span><span>shoulder</span><!-- … 12 張 --></div>
</div>
.reveal .fragment.custom { opacity: 1; visibility: inherit; }
.wall { perspective: 1200px; }
.wall .subs { transform-style: preserve-3d; }
.wall .subs span { opacity: 0; transform: translateZ(-700px) translateX(260px) rotateY(50deg);
transition: transform .8s cubic-bezier(.2,.7,.2,1), opacity .5s; }
.wall.visible .subs span { opacity: 1; transform: none; }
.wall .subs span:nth-child(2) { transition-delay: .06s } /* 每張 +60 ms */
.wall .subs span:nth-child(3) { transition-delay: .12s }
.wall .root { transition: box-shadow .6s .9s, transform .6s .9s; }
.wall.visible .root { box-shadow: 0 0 46px rgba(242,184,75,.55); transform: scale(1.04); } /* 最後一張落定後發光 */
3.5 GSAP 逐字浮現
封面與封底標題一個字一個字彈出。GSAP 3(gsap.min.js 約 70 KB)內嵌即可離線用。
<h1 data-stagger>AI提升教學能力</h1>
function split(el) { // 把文字拆成 <span class="ch">
if (el.dataset.split) return; el.dataset.split = '1';
[...el.childNodes].forEach(n => { if (n.nodeType !== 3) return;
const f = document.createDocumentFragment();
[...n.textContent].forEach(ch => { const s = document.createElement('span');
s.className = 'ch'; s.textContent = ch === ' ' ? ' ' : ch; f.appendChild(s); });
n.replaceWith(f); });
}
function play(slide) {
const h = slide && slide.querySelector('h1[data-stagger]'); if (!h || STATIC || REDUCE) return;
split(h);
gsap.fromTo(h.querySelectorAll('.ch'), { y: 46, opacity: 0, rotateX: -70 },
{ y: 0, opacity: 1, rotateX: 0, duration: .7, stagger: .05, ease: 'back.out(1.7)', overwrite: true });
}
Reveal.on('ready', e => play(e.currentSlide));
Reveal.on('slidechanged', e => play(e.currentSlide));
.reveal h1 .ch { display: inline-block; } /* 沒有這行 transform 不會生效 */
<br> 會保留(只拆文字節點),多行標題可以直接用。
封面底圖上疊一層緩慢飄動的 3D 粒子,滑鼠移動有視差。只在封面跑 render loop,離開就停。
打包(Three.js 新版只有 ES module,要打成一個檔才能內嵌):
npm i three esbuild
npx esbuild three-particles-entry.js --bundle --minify --format=iife --outfile=three-particles.min.js # 約 530 KB
掛到封面背景:
(function () {
if (STATIC || REDUCE || !window.initCoverParticles) return;
let fx = null;
function ensure() { if (fx) return fx; const bg = Reveal.getSlideBackground(0); if (!bg) return null;
bg.style.overflow = 'hidden'; return (fx = window.initCoverParticles(bg, { count: 1600 })); }
function sync() { const f = ensure(); if (!f) return; Reveal.getIndices().h === 0 ? f.start() : f.stop(); }
Reveal.on('ready', sync); Reveal.on('slidechanged', sync);
Reveal.on('resize', () => fx && fx.resize());
})();
模組重點(完整在 examples/three-particles-entry.js):PointsMaterial 配 canvas 畫的圓形柔光 sprite(不需外部圖)、AdditiveBlending、setPixelRatio(Math.min(devicePixelRatio, 1.5))、powerPreference: 'low-power'。
一定要在播放用的電腦接投影機實測一次幀率,舊筆電可能卡。
🎤 講者工具
4.1 Spotlight 聚光燈按 X 開關,滑鼠周圍亮、其餘變暗,滾輪調大小。講截圖細節時用。自己寫約 25 行,不用外掛。
.spot { position: fixed; inset: 0; z-index: 60; pointer-events: none; display: none; }
.spot.on { display: block; }
(function () {
if (STATIC) return;
const c = document.createElement('canvas'); c.className = 'spot'; document.body.appendChild(c);
const ctx = c.getContext('2d'); let on = false, x = innerWidth / 2, y = innerHeight / 2, R = 150;
function draw() { if (!on) return; c.width = innerWidth; c.height = innerHeight;
ctx.fillStyle = 'rgba(0,0,0,0.78)'; ctx.fillRect(0, 0, c.width, c.height);
const g = ctx.createRadialGradient(x, y, R * .55, x, y, R);
g.addColorStop(0, 'rgba(0,0,0,1)'); g.addColorStop(1, 'rgba(0,0,0,0)');
ctx.globalCompositeOperation = 'destination-out'; ctx.fillStyle = g;
ctx.beginPath(); ctx.arc(x, y, R, 0, Math.PI * 2); ctx.fill();
ctx.globalCompositeOperation = 'source-over'; }
addEventListener('mousemove', e => { x = e.clientX; y = e.clientY; on && requestAnimationFrame(draw); }, { passive: true });
addEventListener('wheel', e => { if (on) { R = Math.max(60, Math.min(400, R - e.deltaY * .3)); draw(); } }, { passive: true });
document.addEventListener('keydown', e => {
if ((e.key === 'x' || e.key === 'X') && !e.metaKey && !e.ctrlKey && e.target.tagName !== 'INPUT') {
on = !on; c.classList.toggle('on', on); draw(); } });
})();
4.2 講者計時條
頂端一條細線,40 分鐘走完,超時變紅;按 T 重新計時。和 reveal 底部的頁數進度條對照,一眼看出講快還是講慢。
.timebar { position: fixed; top: 0; left: 0; height: 5px; width: 0; background: #F2B84B; z-index: 50; transition: width 1s linear; }
.timebar.over { background: #E8846B; }
@media print { .timebar { display: none; } }
(function () {
if (STATIC) return;
const ALLOT = 40 * 60 * 1000, bar = document.createElement('div'); bar.className = 'timebar'; document.body.appendChild(bar);
let start = Date.now();
const tick = () => { const p = Math.min(1, (Date.now() - start) / ALLOT); bar.style.width = p * 100 + '%'; bar.classList.toggle('over', p >= 1); };
setInterval(tick, 1000); tick();
document.addEventListener('keydown', e => { if ((e.key === 't' || e.key === 'T') && !e.metaKey && !e.ctrlKey) { start = Date.now(); tick(); } });
})();
4.3 點圖放大(lightbox)
reveal.js 5.2 起內建,加一個屬性就好。
<img src="shot.jpg" data-preview-image title="點一下放大" style="cursor:zoom-in">
<video data-preview-video="demo.mp4"></video>
<a href="https://example.com" data-preview-link>在簡報裡開網頁</a> <!-- 對方網站不能禁止 iframe -->
4.4 頁內可作答的測驗
講「AI 30 秒做出測驗」時,同一頁直接放一個能作答的測驗,現場請人點。比任何特效都有說服力,純 JS、離線可用。
<div class="quiz" id="quiz1"></div>
const Q = [{ q: '題目?', o: ['選項 A', '選項 B', '選項 C', '選項 D'], a: 2, e: '解釋' } /* … */];
let i = 0, score = 0; const box = document.getElementById('quiz1');
function show() {
if (i >= Q.length) { box.innerHTML = `<div class="sc">${score} / ${Q.length}</div>`; return; }
const q = Q[i];
box.innerHTML = `<p>${q.q}</p>` + q.o.map((o, k) => `<button data-k="${k}">${'ABCD'[k]}. ${o}</button>`).join('') + `<div class="fb"></div>`;
box.querySelectorAll('button').forEach(b => b.onclick = () => {
const k = +b.dataset.k; if (k === q.a) score++;
box.querySelectorAll('button').forEach(x => { x.disabled = true; if (+x.dataset.k === q.a) x.classList.add('ok'); });
if (k !== q.a) b.classList.add('bad');
box.querySelector('.fb').textContent = (k === q.a ? '答對。' : `正解是 ${'ABCD'[q.a]}。`) + q.e;
setTimeout(() => { i++; show(); }, 1800);
});
}
show();
醫療或專業題目:題目與解釋要自己審過,參考文獻不確定就標「待查證」。
全場用手機作答、互動地圖、製作技巧頁:見上方 📱 現場互動。
🧪 讓動畫不妨礙 QA、PDF 與無障礙
STATIC 開關與 reduced-motion加了動畫之後,自動截圖會拍到動畫中途、PDF 會少內容。解法是一個 STATIC 開關:
const REDUCE = matchMedia('(prefers-reduced-motion: reduce)').matches;
// Playwright 截圖(navigator.webdriver)或 ?print-pdf 時:關掉所有動畫,顯示最終狀態
const STATIC = navigator.webdriver || location.search.indexOf('print-pdf') >= 0;
if (STATIC) document.documentElement.classList.add('static');
Reveal.initialize({
transition: 'fade', transitionSpeed: 'fast',
autoAnimateDuration: STATIC ? 0 : 0.9,
pdfSeparateFragments: false, // 否則每個 fragment 各印一頁,54 頁變 79 頁
});
html.static .flip .inner, html.static .reveal .fragment, html.static .wall .subs span { transition: none !important; }
@media (prefers-reduced-motion: reduce) {
.flip .inner, .wall .subs span, .reveal .fragment { transition-duration: .01ms !important; }
}
原則:
- GSAP、Three.js、Spotlight、計時條都在開頭寫
if (STATIC || REDUCE) return;。 prefers-reduced-motion只作用在自己加的效果,不要用全域*把 reveal 換頁一起壓掉。- 本 repo 的
qa-screenshots.mjs會先關轉場、展開全部 fragment 再截圖,搭配 STATIC 開關就能拍到最終畫面。
🎚️ 怎麼挑:穩重、中間、驚豔
| 風格 | 用哪些 | 適合 |
|---|---|---|
| 穩重 | fade 換場、章節 zoom、逐步出現、計時條、點圖放大、頁內測驗 | 醫學、學術、教學 |
| 中間 | + Auto-Animate、3D 翻卡、GSAP 封面與收尾 | 工作坊、內訓 |
| 驚豔 | + Three.js 封面、卡片牆飛入、Spotlight | 發表會、開幕、示範「HTML 簡報能做什麼」 |
只放在 4 到 5 個關鍵時刻:封面、章節轉折、核心概念、收尾。其他頁保持乾淨。作者的 55 頁簡報用了 12 種技巧,但每種只出現在一兩頁。
範例簡報的技巧對照頁數| 技巧 | 頁 |
|---|---|
| Three.js 粒子 | 1(封面) |
| GSAP 逐字 | 1、55 |
| 逐步出現 | 4、7、24、50、51、52 |
| 章節 zoom | 所有章節頁 |
| Auto-Animate | 8 → 9 |
| 頁內測驗 | 11 |
| 卡片牆飛入 | 19 |
| 3D 翻卡 | 25 |
| 點圖放大 | 所有截圖頁 |
| Spotlight(X) | 全場,示範在 14、46 |
| 計時條(T) | 全場 |
🕳️ 常見的坑
十二個踩過的雷與解法| 現象 | 原因與解法 |
|---|---|
| 翻卡或卡片牆在點擊前整組看不見 | fragment 預設 opacity:0;加 custom class 並寫 .reveal .fragment.custom { opacity:1; visibility:inherit } |
| 截圖畫面往右偏、像被切掉 | 截到換場中途;截圖前 Reveal.configure({ transition:'none' }) |
| Auto-Animate 頁截圖是半成品 | autoAnimateDuration: STATIC ? 0 : 0.9 |
| PDF 頁數比投影片多 | pdfSeparateFragments: false |
| GSAP 逐字沒有動 | 拆出的 span 要 display:inline-block |
r-fit-text 把整排版面撐爆 |
它會加 nowrap+inline-block,放在 grid 的 1fr 欄會無限撐寬;多行文字改固定 font-size |
| 輸入框打數字就翻頁 | 輸入框的 keydown 要 e.stopPropagation() |
| 外掛鍵位沒反應或行為怪 | 撞到內建鍵(B 黑屏、S 講者、O 總覽);換鍵 |
插入 script 後講者視窗壞掉(RevealNotes is not defined) |
notes.js 原始碼裡也有 </body>;程式插入時找最後一個 </body> |
| 3D 粒子頁卡頓 | 降 count、setPixelRatio 上限 1.5、離開封面就 stop() |
| 投票頁截圖被判溢出 | 隱藏的答案框用 translateY 往下推會撐出捲動範圍;改用 scale |
| 手機投票票數一直是 0 | 測試用的 voter 代號太短被格式檢查擋掉(需 8–64 字元) |
這些規矩是踩過雷才寫的
- 大綱先確認再寫 HTML。 AI 一口氣產 50 頁再改,比先對齊大綱貴十倍。
- QA 必做、要看圖。
montage.png先看,有標記的頁再看大圖;改完至少再驗一輪。 - 每張 QR 都要
data-url。 沒有它 QA 比對不了,占位網址(example.com)會被警告。 - 會場當作沒網路。 交付前
QA_OFFLINE=1;字型走系統堆疊,不下載 Google Fonts。 - 部署、開 repo 前一定先問。 skill 不會自己把東西推到公網。
- QA 腳本會執行簡報裡的 JavaScript。 只對自己產生的 HTML 跑。
目錄
skills/
html-slide-deck/
SKILL.md 主流程(給 agent 讀)
scripts/ qa-screenshots、render-slides、build-handout、print-pdf、inline-assets、poll-slides、
resolve、montage.py、pptx-to-pdf.sh(macOS)、gen-image.sh(可換生圖工具)
references/
reveal-template.html 深色版型:chip/card/grid/steps/mock/qrcard/viz/互動頁(reveal.js 5.2)
offline-build-template/ 做法 B:content.js+build.js+check.mjs+PLAN.md+角色檔
pptxgenjs-helpers.mjs 原生 PPTX 的 theme 與 helper(+ native-pptx-example.mjs)
pitfalls.md 踩過的雷與可複用片段
design-principles.md 規劃與設計原則、現成工具(整理自 X 分享,SKILL.md 第 1 步引用)
interactive-pages.md 互動地圖、製作技巧頁、PPTX 轉 HTML 插入互動頁
live-poll/ 手機即時投票:worker/(Cloudflare Worker+Durable Object)、screen/(投影片端)、load-test.mjs
fixed-rules.md Keanu 的固定規則(SKILL.md 要求遵守)
html-slide-effects/
SKILL.md
references/fixed-rules.md 同上一份固定規則
examples/
workshop-2026/content.js 50 頁工作坊完整內容
pptx-edits-back-to-html.py 講者在 PPTX 手改後回灌 HTML+全部效果的實例(見 examples/README.md)
docs/TECHNIQUES.md 快捷鍵、換場、動畫技巧參考(附程式碼)
docs/index.html Live Demo 成品(GitHub Pages;原始檔在 demo/,cd demo && npm i && npm run build)
demo/ Live Demo 原始檔:src/index.html、polls.json、build.mjs
README.en.md、SECURITY.md 英文說明、安全說明(ADMIN_KEY、QA 會執行 JS、漏洞回報)
docs/html-slides-tips-from-x-2026-10-17.md X 使用者分享的 HTML 簡報技巧與工具整理(含來源連結)
docs/*.png 成果圖
.claude-plugin/ Claude Code plugin/marketplace 設定
AGENTS.md、CLAUDE.md 給在本 repo 工作的 agent
疑難排解
| 現象 | 處理 |
|---|---|
Playwright requires Node.js 20 or higher |
Node 太舊。Ubuntu 24.04 的 apt install nodejs 是 18,改用 nvm 或 nodejs.org 的 Node 20+ |
找不到套件 playwright |
在有 node_modules 的專案目錄執行,或 npm i -g playwright,或設 SKILL_PKG_ROOT |
缺 jsqr/pngjs |
到 skill 目錄 npm install;或 QA_SKIP_QR=1 明確略過 |
| Chromium 沒下載 | npx playwright install chromium;已有 Chromium 就設 CHROMIUM_PATH |
| 截圖中文變方框(Linux) | 裝 Noto Sans CJK |
| PubMed 等站對 headless 回 403 | 請使用者提供截圖,或用 CLI 內建瀏覽器工具(若有) |
osascript -9074/逾時 |
pptx-to-pdf.sh 會重試一次;首次要在螢幕前允許「自動化」;SSH 無法用 |
| PDF 頁數比投影片多 | Reveal.initialize 加 pdfSeparateFragments: false(範本已設) |
來歷
作者是運動醫學科醫師,2026-09 為臨床教師工作坊做了 24 頁附錄與 54 頁主簡報,把過程寫成 skill,再請 Codex、Claude、Grok、Gemini 四個模型審一輪(結果在 CHANGELOG)。範本裡的示例內容屬作者,換成你自己的。
授權
程式碼與腳本 MIT。範本與 examples/ 的示例文字、網址、截圖僅供替換參考;第三方字型與外掛請自行確認授權。
English summary
Two Agent Skills (open SKILL.md format) for building reveal.js HTML slide decks with an AI coding agent, plus the scripts that make them trustworthy:
- html-slide-deck: outline-first workflow, dark professional template, real screenshots, QR codes, interactive pages, live audience polling from phones (Cloudflare Worker), per-slide screenshot QA (overflow, JS errors, external requests, QR decoded and compared with
data-url), export to handout PPTX (notes included), PDF, and native editable PPTX. - html-slide-effects: fragments, Auto-Animate, 3D flip cards, GSAP, timer bar, lightbox, scroll view, with reduced-motion and a STATIC switch so QA screenshots and PDFs capture the final state.
Works with Claude Code, OpenAI Codex CLI, Gemini CLI and Grok Build (./install.sh detects them). Docs are in Traditional Chinese; the scripts and templates are language-neutral.
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found