# html-ppt — 震撼级单文件 HTML 演示生成规范

> 用途:把任意输入内容(报告 / 文档 / 数据 / 笔记)转成 **单文件、离线可用、支持翻页遥控器** 的 PPT 式 HTML 演示。
> 目标:打开第一页 3 秒内让观众"哇"出来;翻完全片觉得这是发布会级作品,而不是 AI 生成的模板。
> 使用方式:把本文档全文 + 你的原始材料一起交给任意 AI 模型,要求它"严格按此规范执行"。

---

## 零、三条铁律(任何情况下不许违反)

1. **单文件、零外链。** 所有 CSS / JS / 字体回退 / 图形全部内联,不引用任何 CDN、外部字体、外部图片。演示现场经常没网,外链 = 现场裸奖。图形一律用 CSS / SVG / Canvas 画,不用图片。
2. **不编造数据。** 所有数字、事实、引语必须来自输入材料。材料里没有的,宁可不写,不许脑补。
3. **每一页在进入的瞬间必须"看起来是完整的"。** 不允许出现"按了键才有内容、不按键像坏了"的空页(这是最高频翻车点,见第六节)。

---

## 一、先想清楚再写代码(内容优先)

**在写任何 HTML 之前**,先完成并输出这份大纲,再动手:

1. 通读输入材料,提炼:**一条主线故事**(整个演示回答什么问题)、**5±2 个核心判断**、**所有值得放大的数字**。
2. 排片 10–20 页,每页只讲**一件事**。讲不完就拆页,不许堆。
3. 每页标题必须是**观点句**,不是名词:
   - ❌ "市场分析" / "产品介绍" / "数据概览"
   - ✅ "监管换挡,主战场转向存量价值挖掘" / "31 秒到账,是这个模式的全部底气"
4. 输出三列大纲:`页码 | 观点式标题 | 这页的核心视觉(大数字?对比条?流程链?)`。
5. 标准叙事弧:封面钩子 → 目录 → 核心判断 → 背景/沿革 → 拆解主体(3–8 页,数据密度最高)→ 风险/反面 → 结论/行动 → 金句收尾页。

---

## 二、定气质(反 AI 味守则)

### 禁令清单(出现任何一条即判定为"AI 模板脸",必须重做)

- 紫蓝渐变背景 + 玻璃拟态白色圆角卡片阵列
- 每页三个并排圆角卡片、卡片里放 emoji 当图标(emoji 禁止作图标使用)
- 全片只用一种版式、一种字号层级
- Inter/Roboto 默认字体、无任何字号对比(标题和正文差距 < 2 倍)
- 居中大标题 + 副标题 + 三点列表的"模板脸"结构页超过 2 页

### 正面要求

- **从内容领域出发选一种气质**,四套现成方案(也可自创,但要报出完整色板):

| 气质 | 适用 | 背景 | 文字 | 强调 | 语义色 |
|---|---|---|---|---|---|
| 暗夜指挥舱 | 金融/商业情报/战略 | `#050E17` | `#EAF2FA` | 金 `#F7C74F` + 蓝 `#35A2F5` | 正 `#23D58C` 负 `#FF7A59` |
| 纸面杂志 | 人文/品牌/教育 | `#F5F1E8` | `#1A1712` | 朱红 `#C3422F` | 墨蓝 `#1F3A5F` |
| 荧光终端 | 科技/开发者/极客 | `#0A0E0A` | `#D8F3DC` | 荧绿 `#39FF88` | 琥珀 `#FFB300` |
| 深空画廊 | 消费/生活方式 | `#14100E` | `#F5EDE4` | 橙 `#FF8C42` | 青 `#4ECDC4` |

- 全片色板 **≤ 5 色**:1 背景 + 1 主文字 + 1 强调 + 2 语义(正/负)。强调色是稀缺资源,只给最重要的词和数字。
- **字体三层制**:标题用衬线(`"Noto Serif SC","Songti SC",serif`)或超大字重;正文用系统无衬线;**所有数字一律等宽字体** + `font-variant-numeric:tabular-nums`。中文字体必须带回退链(PingFang SC / Microsoft YaHei)。
- **一个签名元素**贯穿全片:与主题相关的符号粒子背景(如金融用 ₦/$/¥)、巨型半透明页码水印、扫描线、装饰性角标——任选其一做足,不要三心二意。
- 所有尺寸用 **vh/vw**,禁止 px 定字号(投影仪/大屏上比例才不会崩)。标题 5–9vh,正文 2–2.6vh,大数字 10–14vh。

---

## 三、引擎骨架(直接复制,不要重写)

下面是**已在真实项目里验证过的完整引擎**。使用规则:

- 只允许改:`:root` 色板变量、签名元素(粒子符号)、`<section class="slide">` 里的内容。
- **引擎 JS 与 HUD 结构一个字都不要动**——它已处理好所有边界情况(Esc 冲突、计数器重复触发、总览模式白屏等)。
- 能力强的模型可以增加转场变体和签名元素,但**按键协议和自查清单不可删减**。

```html
<!doctype html><html lang="zh"><head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>演示标题</title>
<style>
:root{--bg0:#050E17;--bg1:#0B1B2B;--ink:#EAF2FA;--mut:#8FA6BC;--acc:#F7C74F;--acc2:#35A2F5;
--pos:#23D58C;--neg:#FF7A59;
--disp:"Noto Serif SC","Songti SC","STSong",serif;
--sans:system-ui,"PingFang SC","Microsoft YaHei",sans-serif;
--mono:"SF Mono",ui-monospace,"Cascadia Mono",Consolas,monospace}
*{margin:0;padding:0;box-sizing:border-box}
html,body{height:100%;overflow:hidden;background:var(--bg0);color:var(--ink);font-family:var(--sans)}
#bgc{position:fixed;inset:0;z-index:0;opacity:.6}
#stage{position:fixed;inset:0;z-index:1;perspective:1700px}
.slide{position:absolute;inset:0;padding:7vh 7vw;display:flex;flex-direction:column;justify-content:center;
opacity:0;visibility:hidden;transform:translateX(8vw) rotateY(-10deg);filter:blur(6px);
transition:transform .85s cubic-bezier(.72,0,.22,1),opacity .85s,filter .85s,visibility 0s .85s}
.slide.active{opacity:1;visibility:visible;transform:none;filter:none;
transition:transform .85s cubic-bezier(.72,0,.22,1),opacity .85s,filter .85s}
.slide.past{transform:translateX(-8vw) rotateY(10deg)}
/* 转场变体:在 section 上加类,给关键页换口味 */
.fx-zoom{transform:scale(.75)}.fx-zoom.past{transform:scale(1.25)}
.fx-rise{transform:translateY(10vh)}.fx-rise.past{transform:translateY(-10vh)}
/* 排版 */
.kick{font:600 1.9vh var(--mono);letter-spacing:.35em;color:var(--acc);text-transform:uppercase;margin-bottom:2vh}
h1{font:700 8.5vh/1.25 var(--disp)}
h2{font:700 5.2vh/1.3 var(--disp);margin-bottom:4vh}
h1 em,h2 em{font-style:normal;color:var(--acc)}
.num{font-family:var(--mono);font-variant-numeric:tabular-nums}
.mut{color:var(--mut)}
/* 入场:自动级联(默认全部用这个) */
.slide .auto{opacity:0}
.slide.active .auto{animation:riseIn .7s cubic-bezier(.2,.7,.3,1) both}
@keyframes riseIn{from{opacity:0;transform:translateY(3.5vh)}to{opacity:1;transform:none}}
/* 手动分步(全片最多 3 页用) */
.fr{opacity:0;transform:translateY(2.5vh);transition:opacity .45s,transform .45s}
.fr.on{opacity:1;transform:none}
/* 数据动画:一律由 .slide.active 触发 */
.bar{height:3.4vh;background:#ffffff10;border-radius:.6vh;overflow:hidden}
.fill{height:100%;width:0;background:var(--acc2);transition:width 1.1s .35s cubic-bezier(.2,.7,.2,1)}
.slide.active .fill{width:var(--w)}
/* HUD */
#prog{position:fixed;top:0;left:0;height:.5vh;background:linear-gradient(90deg,var(--acc),var(--acc2));
width:0;z-index:9;transition:width .5s}
#pn{position:fixed;right:2.5vw;bottom:3vh;font:600 2vh var(--mono);color:var(--mut);z-index:9}
#pn b{color:var(--acc)}
#dots{position:fixed;left:50%;bottom:3vh;transform:translateX(-50%);display:flex;gap:1vh;z-index:9}
#dots i{width:1vh;height:1vh;border-radius:50%;background:#ffffff28;cursor:pointer;transition:.3s}
#dots i.on{background:var(--acc);transform:scale(1.4)}
#black{position:fixed;inset:0;background:#000;opacity:0;pointer-events:none;transition:.35s;z-index:20}
#black.on{opacity:1;pointer-events:auto}
#help{position:fixed;inset:0;background:#000c;backdrop-filter:blur(8px);display:none;place-items:center;z-index:21}
#help.on{display:grid}
#help table{font:2vh var(--mono);border-collapse:collapse}
#help td{padding:.8vh 2vw;border-bottom:1px solid #ffffff18}
#help kbd,.keys kbd{font:600 1.8vh var(--mono);border:1px solid #ffffff35;border-radius:.5vh;
padding:.2vh 1vh;color:var(--acc)}
/* 总览模式 */
#stage.ov .slide{opacity:1;visibility:visible;filter:none;cursor:pointer;
border:1px solid #ffffff22;border-radius:1.2vh;
transition:transform .55s cubic-bezier(.2,.7,.2,1),opacity .55s}
#stage.ov .auto{animation:none!important;opacity:1!important}
#stage.ov .fr{opacity:1!important;transform:none!important}
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}
.slide .auto,.fr{opacity:1!important;transform:none!important}.slide.active .fill{width:var(--w)}}
</style></head><body>
<canvas id="bgc"></canvas>
<div id="stage">

<!-- ▼▼ 每页一个 section.slide,以下三页是示例,按大纲替换 ▼▼ -->
<section class="slide fx-zoom">
  <div class="kick">INTERNAL BRIEF · 2026</div>
  <h1>观点式主标题,<br><em>关键词高亮</em></h1>
  <p class="auto mut" style="font-size:2.6vh;margin-top:3vh">一句话副标题</p>
  <p class="auto keys mut" style="margin-top:6vh;font-size:2vh">
    <kbd>→</kbd> 翻页 <kbd>O</kbd> 总览 <kbd>?</kbd> 帮助</p>
</section>

<section class="slide">
  <h2>数据页示例:<em>数字要动起来</em></h2>
  <div class="auto">
    <div style="display:flex;justify-content:space-between;font-size:2.2vh;margin-bottom:1vh">
      <span>指标 A</span><b class="num" data-count="1234">0</b></div>
    <div class="bar"><div class="fill" style="--w:78%"></div></div>
  </div>
</section>

<section class="slide fx-rise">
  <h2 style="text-align:center">结尾金句,<em>一句话</em>,不解释</h2>
</section>
<!-- ▲▲ slides 结束 ▲▲ -->

</div>
<div id="prog"></div><div id="pn"></div><div id="dots"></div><div id="black"></div>
<div id="help"><table>
<tr><td><kbd>→ ↓ Space PgDn</kbd></td><td>下一步 / 下一页</td></tr>
<tr><td><kbd>← ↑ PgUp</kbd></td><td>上一步 / 上一页</td></tr>
<tr><td><kbd>O / Esc</kbd></td><td>总览网格</td></tr>
<tr><td><kbd>B / .</kbd></td><td>黑屏暂停</td></tr>
<tr><td><kbd>F</kbd></td><td>全屏</td></tr>
<tr><td><kbd>A</kbd></td><td>自动播放</td></tr>
<tr><td><kbd>Home / End</kbd></td><td>首页 / 末页</td></tr>
</table></div>
<script>
const stage=document.getElementById('stage'),slides=[...stage.querySelectorAll('.slide')],N=slides.length;
const frags=slides.map(s=>[...s.querySelectorAll('.fr')]),shown=slides.map(()=>0);
const pn=document.getElementById('pn'),dots=document.getElementById('dots'),
black=document.getElementById('black'),help=document.getElementById('help');
let cur=0,ov=false,auto=null;
slides.forEach((s,i)=>{const d=document.createElement('i');
d.onclick=()=>{shown[i]=0;go(i)};dots.appendChild(d);
s.addEventListener('click',()=>{if(ov){toggleOv();go(i);}});});
function runCounters(root){root.querySelectorAll('[data-count]').forEach(el=>{
if(el.dataset.done)return;const f=el.closest('.fr');if(f&&!f.classList.contains('on'))return;
el.dataset.done=1;const end=parseFloat(el.dataset.count),dec=+(el.dataset.dec||0),
dur=+(el.dataset.dur||1400),t0=performance.now();
(function tick(t){const p=Math.min(1,(t-t0)/dur),v=end*(1-Math.pow(1-p,3));
el.textContent=v.toFixed(dec).replace(/\B(?=(\d{3})+(?!\d))/g,',');
if(p<1)requestAnimationFrame(tick);})(t0);});}
function sync(){slides.forEach((s,i)=>{s.classList.toggle('active',i===cur&&!ov);
s.classList.toggle('past',i<cur);
frags[i].forEach((f,j)=>f.classList.toggle('on',j<shown[i]));
if(i===cur)s.querySelectorAll('.auto').forEach((el,k)=>el.style.animationDelay=(.15+k*.09)+'s');});
document.getElementById('prog').style.width=(N>1?cur/(N-1)*100:100)+'%';
pn.innerHTML='<b>'+(cur+1)+'</b> / '+N;
[...dots.children].forEach((d,i)=>d.classList.toggle('on',i===cur));
setTimeout(()=>{if(!ov)runCounters(slides[cur])},350);}
function go(n){cur=Math.max(0,Math.min(N-1,n));sync();}
function next(){if(shown[cur]<frags[cur].length){shown[cur]++;sync();}
else if(cur<N-1){shown[cur+1]=0;go(cur+1);}}
function prev(){if(shown[cur]>0){shown[cur]--;sync();}else if(cur>0)go(cur-1);}
function toggleOv(){ov=!ov;stage.classList.toggle('ov',ov);
if(ov){const cols=Math.ceil(Math.sqrt(N)),rows=Math.ceil(N/cols),sc=Math.min(.9/cols,.84/rows);
slides.forEach((s,i)=>{const c=i%cols,r=Math.floor(i/cols);
s.style.transform=`translate(${(c-(cols-1)/2)*(96/cols)}vw,${(r-(rows-1)/2)*(92/rows)}vh) scale(${sc})`;});}
else slides.forEach(s=>s.style.transform='');
sync();}
function toggleAuto(){if(auto){clearInterval(auto);auto=null;}
else auto=setInterval(()=>{if(cur===N-1&&shown[cur]>=frags[cur].length)toggleAuto();else next();},4000);}
addEventListener('keydown',e=>{const k=e.key;
if(k==='?'){help.classList.toggle('on');return;}
if(k==='Escape'){if(help.classList.contains('on'))help.classList.remove('on');
else if(black.classList.contains('on'))black.classList.remove('on');else toggleOv();return;}
if(k==='o'||k==='O'){toggleOv();return;}
if(ov)return;
if(k==='b'||k==='B'||k==='.'){black.classList.toggle('on');return;}
if(k==='f'||k==='F'){document.fullscreenElement?document.exitFullscreen()
:document.documentElement.requestFullscreen();return;}
if(k==='a'||k==='A'){toggleAuto();return;}
if(['ArrowRight','ArrowDown','PageDown',' ','Enter'].includes(k)){e.preventDefault();next();}
if(['ArrowLeft','ArrowUp','PageUp'].includes(k)){e.preventDefault();prev();}
if(k==='Home')go(0);if(k==='End')go(N-1);});
/* 触屏滑动 */
let tx=0;addEventListener('touchstart',e=>tx=e.touches[0].clientX,{passive:true});
addEventListener('touchend',e=>{const dx=e.changedTouches[0].clientX-tx;
if(Math.abs(dx)>60)dx<0?next():prev();},{passive:true});
/* 签名元素示例:主题符号粒子背景(GLYPHS 换成与内容相关的符号,如 ₦ $ ¥ ★) */
const cv=document.getElementById('bgc'),cx=cv.getContext('2d');
let W,H,ps=[],mx=.5,my=.5;const GLYPHS=['◆','★','%','§'];
function rs(){W=cv.width=innerWidth*devicePixelRatio;H=cv.height=innerHeight*devicePixelRatio;}
rs();addEventListener('resize',rs);
addEventListener('mousemove',e=>{mx=e.clientX/innerWidth;my=e.clientY/innerHeight;});
for(let i=0;i<80;i++)ps.push({x:Math.random(),y:Math.random(),z:.3+Math.random()*.7,
vy:.00016+Math.random()*.0004,g:GLYPHS[Math.random()*GLYPHS.length|0],gold:Math.random()<.3});
(function loop(){cx.clearRect(0,0,W,H);
ps.forEach(p=>{p.y-=p.vy;if(p.y<-.05){p.y=1.05;p.x=Math.random();}
const px=p.x*W+(mx-.5)*36*p.z*devicePixelRatio,py=p.y*H+(my-.5)*24*p.z*devicePixelRatio;
cx.globalAlpha=.14*p.z;cx.fillStyle=p.gold?'#F7C74F':'#5C7A96';
cx.font=(9+p.z*13)*devicePixelRatio+'px monospace';cx.fillText(p.g,px,py);});
cx.globalAlpha=1;requestAnimationFrame(loop);})();
sync();
</script></body></html>
```

---

## 四、页型模式库(按大纲对号入座)

每页从下面选一种"核心视觉",全片至少用到 5 种不同页型:

1. **封面页**:`fx-zoom` + 8–9vh 观点标题(关键词 `<em>` 高亮)+ 底部一排关键数据小条(用计数器)+ 按键提示。第一页就要有动的东西。
2. **目录页**:可点击网格,每格 `onclick="go(n)"`(n 为目标页序号,从 0 数)。
3. **大数字页**:一个 10–14vh 的等宽数字撑满视觉中心,`<b class="num" data-count="77399.6" data-dec="1">0</b>`,配一行小字解释。一页只放一个主数字。
4. **对比条形页**:多行 `.bar > .fill`,每行 `--w` 按真实比例;竞对/多项对比首选。**不要给条形加 `.fr`**,进页一起生长最有气势。
5. **环形占比页**(SVG,进页自动画弧):

```html
<svg viewBox="0 0 120 120" style="width:26vh">
  <circle cx="60" cy="60" r="54" fill="none" stroke="#ffffff14" stroke-width="10"/>
  <circle class="val" cx="60" cy="60" r="54" fill="none" stroke="var(--acc)" stroke-width="10"
    stroke-linecap="round" stroke-dasharray="339.3" stroke-dashoffset="339.3"
    transform="rotate(-90 60 60)"/>
</svg>
<!-- CSS 加一条:
.slide.active .val{stroke-dashoffset:33.9;transition:stroke-dashoffset 1.3s .4s cubic-bezier(.2,.7,.2,1)}
dashoffset = 339.3 × (1 − 占比),例:90% → 33.9 -->
```

6. **左右对峙页**:两个半屏,一半用 `--pos` 一半用 `--neg`(表面 vs 真相、宣传 vs 实际),中间可加一道 SVG 裂缝/闪电做戏剧张力。
7. **时间轴页**:横向轴线 + 节点 `.auto` 级联入场,每节点"年份(等宽)+ 一句事件"。
8. **流程链页**:节点 + 箭头,节点数字用计数器;适合"XX 只需 31 秒"类叙事,可用 `.fr` 逐步点亮(这是少数该用 `.fr` 的场景)。
9. **四象限页**(SWOT 等):2×2 网格 `.auto` 级联,四格用不同边框色区分。
10. **金句收尾页**:`fx-rise` + 居中一句话,大量留白,不放任何图表。

---

## 五、动效纪律

- **默认一律用 `.auto` 级联入场**(引擎自动按 0.09s 间隔编排),进页即完整、有节奏。
- **`.fr` 手动分步全片最多 3 页**,只给"需要跟着讲述节奏逐条抛出"的叙事关键页(核心判断、流程揭秘)。滥用 `.fr` = 每页进去都像白屏坏页。
- 所有数据动画由 `.slide.active` 触发:条形用 `--w`、圆环用 `dashoffset`、数字用 `data-count`。**禁止进页就静止的死数字**——凡是重要数字必须有计数动画。
- 转场默认 3D 推移已足够炫,`fx-zoom` 给封面/高潮页、`fx-rise` 给收尾页,变体页占比 ≤ 1/3,多了就晕。
- `prefers-reduced-motion` 的降级规则已在骨架里,不许删。

---

## 六、真实翻车点备忘(每条都是踩过的坑)

1. **分步动画滥用** → 页面进入时只有标题、像渲染失败。默认 `.auto`,`.fr` 限量。
2. **内容超出一屏** → `overflow:hidden` 只是遮羞布。每页内容必须在 16:9 一屏内放得下,放不下就拆页或删字。文字多的页每块 ≤ 3 行。
3. **绝对定位元素叠到标题上**(窗口偏矮时)→ 装饰环/大图形要给容器留 `margin-top`,并用 `min(Xvh, Yvw)` 限制尺寸。
4. **px 定字号** → 投影仪上崩。全部 vh/vw。
5. **忘记中文字体回退** → Windows 上衬线标题变默认宋体,气质全无。字体栈必须含 PingFang SC / Microsoft YaHei。
6. **计数器重复触发** → 引擎已用 `dataset.done` 防重,别自己另写计数器。
7. **Esc 键冲突**(关帮助时误入总览)→ 引擎已按"帮助 > 黑屏 > 总览"优先级处理,别改键盘逻辑。
8. **总览模式白屏** → 根源是 `.slide` 的 `visibility 0s .85s` 延迟,引擎的 `#stage.ov` 规则已覆写,别删。
9. **外链字体/库** → 断网现场直接裸奔。零外链。
10. **上下文小的模型一次写不完长文件** → 先写完整骨架(引擎 + 空 section 占位),再分批填页,每批 3–5 页,避免截断产出半个标签。

---

## 七、交付前自查清单(逐项过,任何一条不过就修完再交)

- [ ] 大纲阶段的每一页都已实现,页数与目录一致,目录可点击跳转
- [ ] 标签配平:`<section` 与 `</section>` 数量相等,`<div` 与 `</div>` 数量相等
- [ ] JS 无语法错误(有运行环境就 `node --check`;没有就把 `<script>` 从头到尾通读一遍)
- [ ] 逐页在脑中(或截图)过一遍:**每页进入瞬间是否完整?** 有没有依赖按键才出现主体内容的页
- [ ] 每个重要数字都有计数动画;每个条形/圆环都会生长;没有一页是纯静态文字墙
- [ ] 标题全部是观点句;强调色没有被滥用(每页高亮 ≤ 2 处)
- [ ] PageUp/PageDown(翻页遥控器)、方向键、空格、O 总览、B 黑屏、F 全屏、? 帮助全部可用
- [ ] 禁令清单零命中:无紫蓝渐变、无 emoji 图标、无三卡片模板脸连发
- [ ] 单文件、零外链、无编造数据
- [ ] 若环境支持无头浏览器:截图封面 + 3 张内容页 + 总览模式,亲眼确认后再交付

---

## 八、交付话术

交付时给用户:文件本体 + 一句话操控说明:

> 用浏览器打开即可演示。← → / PageUp PageDown(兼容翻页遥控器)翻页,O 总览,B 黑屏,F 全屏,? 查看全部快捷键。
