Files
MohZhuang 66b61002f2 chore: prepare html-explainer v1.2.2 for public release
First public release of the html-explainer skill: deterministic HTML-to-MP4 explainer video pipeline for AI coding agents.

Additions since v1.2.1:

- scripts/lint_frames.py: pre-render audit of the eight frame-contract rules
- scripts/peek_frame.mjs: single-scene screenshot preview (seconds, not minutes)
- scripts/check_integrity.py: version/catalog/count consistency + stray-CDN scan
- Bilingual README (README.md Chinese default, README.en.md English)
- LICENSE (MIT), THIRD_PARTY_NOTICES.md, licenses/Apache-2.0.txt
- CHANGELOG.md, CONTRIBUTING.md, .gitignore, .gitattributes
- GitHub CI workflow and issue/PR templates

Fixes and docs:

- document lint_frames.py and peek_frame.mjs, previously absent from the file table
- add the lint stage to the pipeline order
- remove references to private client projects so the skill can ship publicly
- record the full upstream attribution chain for the style catalog
- package_skill.py now excludes repo-hygiene files and keeps licence files
2026-09-21 12:46:23 +08:00

180 lines
8.1 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>场景模板 · <id>(复制为 frames/<id>.html 后改内容)</title>
<!--
============================================================
html-explainer 场景契约(八条,缺一出片就坏 / 质量就掉)
============================================================
1) 画布 1920×1080,系统字体栈(绝不引 Google Fonts / CDN 字体 ——
渲染器有 5s 字体硬顶,外部字体会让整帧退化)
2) 全部颜色从 ../theme.css 的 CSS 变量取(--accent / --signal / --warn /
--ok / 灰系),画面代码禁止写死色值 —— 换主题只重写那一个文件
3) 颜色即语义:accent=重点/我们的 · signal=指标/另一方 · warn=警示 ·
ok=正确 · 灰=未激活
4) 动画一律走 GSAP 时间轴:
var tl = gsap.timeline({ paused: true });
window.__tl = tl; ← 渲染器只认这个(或 __timelines)
渲染器逐帧 tl.pause(t) 确定性 seek —— 所以**不要** CSS transition 做入场
(@keyframes 循环装饰可用,渲染器会同步 seek 它)
5) 节拍对齐解说吐字:tl.fromTo(..., B('焦虑缓解')) —— B() 来自
<id>.beats.js(subs.py 自动生成/刷新),改词后帧代码零改动
6) 底部 80–170px 是字幕带(渲染器注入 44px 硬字幕)——内容一律收在
bottom ≥ 170px,入场轨迹也不许穿过
7) 每帧一个视觉焦点:主角 ≥170px 高或 ≥96px 大字,带 accent 柔光/描边;
配角不发光;文字 ≥22px;背景只有幕底+网格,不撒碎屑
8) 帧尾留 0.3–0.9s 的静止呼吸(配音裁尾后有一小段自然静默),
动画主体节拍压在 speech_end 之前(B()/Be() 天然保证)
============================================================
-->
<link rel="stylesheet" href="../theme.css">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
width: 1920px; height: 1080px; overflow: hidden;
background: var(--bg-0);
font-family: 'Microsoft YaHei', 'PingFang SC', 'Noto Sans CJK SC', 'Source Han Sans SC', sans-serif;
-webkit-font-smoothing: antialiased;
color: var(--text);
}
/* 幕底:主题径向光 + 网格(可整帧换成星点/点阵,见 frame-contract.md) */
.bg { position: absolute; inset: 0; background: var(--bg-radial); }
.grid {
position: absolute; inset: 0; opacity: .40;
background-image:
linear-gradient(var(--line-soft) 1px, transparent 1px),
linear-gradient(90deg, var(--line-soft) 1px, transparent 1px);
background-size: 96px 96px;
}
/* 章节角标(配角:不发光) */
.section-no {
position: absolute; left: 120px; top: 88px;
display: flex; align-items: center; gap: 18px;
font-size: 22px; letter-spacing: .32em; color: var(--accent); font-weight: 700;
}
.section-no .bar { width: 56px; height: 3px; background: var(--accent); }
/* ---- 主角区:对比双卡(示例版式,可换) ---- */
.stage { position: absolute; left: 0; right: 0; top: 216px; height: 400px; }
.card {
position: absolute; width: 600px; height: 340px;
border-radius: 24px; padding: 46px 50px;
display: flex; flex-direction: column; justify-content: center;
}
.card.neutral {
left: 180px; top: 0;
background: var(--card-bg); border: 2px solid var(--line);
}
.card.focus {
right: 180px; top: 0;
background: var(--accent-soft); border: 2px solid var(--accent-border);
box-shadow: 0 0 90px var(--accent-glow) inset;
}
.card .label { font-size: 24px; font-weight: 800; letter-spacing: .18em; margin-bottom: 22px; }
.card.neutral .label { color: var(--text-dim); }
.card.focus .label { color: var(--accent); }
.card .thing { font-size: 74px; font-weight: 900; line-height: 1.12; }
.card.focus .thing { text-shadow: 0 0 50px var(--accent-glow); }
.card .note { margin-top: 20px; font-size: 26px; line-height: 1.55; color: var(--text-dim); }
/* ---- 结论带(次主角) ---- */
.verdict { position: absolute; left: 120px; right: 120px; top: 660px; text-align: center; }
.verdict .line { font-size: 60px; font-weight: 900; letter-spacing: -.01em; line-height: 1.3; }
.verdict .line em { font-style: normal; color: var(--accent); text-shadow: 0 0 50px var(--accent-glow); }
/* ---- 证据条(配角;注意 bottom:176px —— 字幕带禁区之上) ---- */
.evidence {
position: absolute; left: 250px; right: 250px; bottom: 176px;
background: var(--card-bg); border: 1px solid var(--line);
border-left: 5px solid var(--accent); border-radius: 14px;
padding: 26px 40px; display: flex; align-items: center; gap: 30px;
}
.evidence .tag {
flex: none; font-size: 23px; font-weight: 800; letter-spacing: .14em;
color: var(--bg-0); background: var(--accent); padding: 10px 20px; border-radius: 7px;
}
.evidence .body { font-size: 32px; line-height: 1.5; color: var(--text); opacity: .88; }
</style>
</head>
<body>
<div class="bg"></div>
<div class="grid"></div>
<div class="section-no"><span class="bar"></span>01 · 开场</div>
<div class="stage">
<div class="card neutral">
<div class="label">左边卡片 · 中性</div>
<div class="thing">概念A</div>
<div class="note">配角不发光 · 灰系表达「未激活」</div>
</div>
<div class="card focus">
<div class="label">右边卡片 · 主题色</div>
<div class="thing">概念B</div>
<div class="note">主角带柔光 · <em style="color:var(--accent)">accent</em> 只给当前重点</div>
</div>
</div>
<div class="verdict">
<div class="line">结论句 · 关键词用 <em>主题色</em> 强调</div>
</div>
<div class="evidence">
<div class="tag">佐证</div>
<div class="body">证据/数字条 —— 事实必须有调研出处(research/调研.md 带 URL)</div>
</div>
<!-- GSAP 本地内置(技能 assets/,随项目复制)——不引任何 CDN -->
<script src="../assets/gsap.min.js"></script>
<!-- 节拍器:subs.py 生成,B('块文本') 返回该词起播秒。占位桩只为模板可预览 -->
<script src="TEMPLATE_ID.beats.js"></script>
<script>
(function () {
if (!window.gsap) { console.error('gsap 未加载(assets/gsap.min.js 缺失)'); return; }
var tl = gsap.timeline({ paused: true });
// ↓↓↓ 节拍全部用 B():解说词/配音改动 → 重跑 tts+subs → 时间自动刷新 ↓↓↓
var t0 = window.B ? B('左边卡片') : 0.4; // 兜底数字只在 beats.js 缺失时用
var t1 = window.B ? B('右边卡片') : 1.8;
var t2 = window.B ? B('结论句') : 3.6;
var t3 = window.B ? B('证据') : 6.0;
tl.fromTo('.section-no', { opacity: 0, x: -34 },
{ opacity: 1, x: 0, duration: 0.6, ease: 'power3.out' }, Math.max(0.05, t0 - 0.25));
tl.fromTo('.card.neutral', { opacity: 0, x: -54 },
{ opacity: 1, x: 0, duration: 0.7, ease: 'power3.out' }, t0);
tl.fromTo('.card.neutral .thing', { opacity: 0, y: 22 },
{ opacity: 1, y: 0, duration: 0.55, ease: 'power2.out' }, t0 + 0.4);
tl.fromTo('.card.focus', { opacity: 0, x: 54 },
{ opacity: 1, x: 0, duration: 0.7, ease: 'power3.out' }, t1);
tl.fromTo('.card.focus .thing', { opacity: 0, scale: .82, transformOrigin: 'center' },
{ opacity: 1, scale: 1, duration: 0.6, ease: 'back.out(1.7)' }, t1 + 0.35);
tl.fromTo('.card.focus', { boxShadow: '0 0 0 rgba(0,0,0,0) inset' },
{ boxShadow: '0 0 90px var(--accent-glow) inset', duration: 0.8, ease: 'sine.out' }, t1 + 0.5);
tl.fromTo('.verdict', { opacity: 0, y: 34 },
{ opacity: 1, y: 0, duration: 0.7, ease: 'power3.out' }, t2);
tl.fromTo('.verdict .line em', { opacity: 0, scale: .7, transformOrigin: 'center' },
{ opacity: 1, scale: 1, duration: 0.55, ease: 'back.out(2)' }, t2 + 0.5);
tl.fromTo('.evidence', { opacity: 0, y: 30 },
{ opacity: 1, y: 0, duration: 0.65, ease: 'power3.out' }, t3);
// ---------- 注册(渲染器只认 window.__tl) ----------
window.__tl = tl;
// 浏览器直接打开预览时自动起播;渲染器下(__MG_RENDER__)由它逐帧 seek
if (!window.__MG_RENDER__) {
requestAnimationFrame(function () { requestAnimationFrame(function () { tl.play(0); }); });
}
})();
</script>
</body>
</html>