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

159 lines
7.4 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>封面模板 · 复制为 frames/cover_169.html 与 frames/cover_34.html</title>
<!--
============================================================
封面帧契约(vs 场景帧,只有四处差别)
============================================================
1) 尺寸不同:
frames/cover_169.html → 1920×1080(抖音主封面)
frames/cover_34.html → 1440×1080(兼容主页 3:4 栅格,防切字)
本模板默认写 1920×1080;做 3:4 版时把 html/body 的宽高与 .stage 布局改掉。
2) **两份是独立排版,不是裁切关系**。16:9 居中裁成 3:4 会丢掉 57.8% 的
画面宽度,大字钩子必被切。所以:同一套视觉基因(配色/幕底/主视觉/钩子),
两套版式各自摆位。
3) 不注入字幕层与进度条(封面不要字幕)——所以底部无禁区,
但仍要留 ≥96px 安全边距(平台会在四边再裁一点)。
4) 封面允许纯静态(无 GSAP)。有 GSAP 时间轴就 seek 到末尾取"完整态",
没有就原样截。入场动画的存在意义是让你能在 --at 时点挑中间态。
============================================================
【封面三要素】(质检就看这三条,缺一即返工)
------------------------------------------------------------
A. 大字钩子 —— 一句能让人停下的反差句。字号 ≥96px(3:4 版可到 120–150px),
必须是画面上最大最亮的东西。关键词用 <em> + accent 色 + 柔光。
B. 核心悖论视觉 —— 画面主体本身要在"讲一个矛盾":
两个对立的数字 / 一升一降的曲线 / 一半亮一半暗的对照 / 撞在一起的两块。
不要放中性的装饰图形。
C. 信息余量 —— 平台会在四边再裁,钩子距边 ≥96px;不要有贴边文字。
============================================================
-->
<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);
}
/* 幕底(与场景帧同一基因,封面可以更浓) */
.bg { position: absolute; inset: 0; background: var(--bg-radial); }
.grid {
position: absolute; inset: 0; opacity: .34;
background-image:
linear-gradient(var(--line-soft) 1px, transparent 1px),
linear-gradient(90deg, var(--line-soft) 1px, transparent 1px);
background-size: 96px 96px;
}
/* 封面专用的强调光晕:两团对立色,视觉上就在"对抗" */
.glow-a {
position: absolute; width: 1100px; height: 1100px; left: -220px; top: -260px;
background: radial-gradient(circle, var(--accent-glow) 0%, transparent 62%); opacity: .30;
}
.glow-b {
position: absolute; width: 900px; height: 900px; right: -180px; bottom: -240px;
background: radial-gradient(circle, var(--signal) 0%, transparent 64%); opacity: .18;
}
/* ---------- 核心悖论视觉(示例:升降对照) ---------- */
.paradox { position: absolute; right: 130px; top: 250px; width: 660px; }
.prow { display: flex; align-items: baseline; justify-content: flex-end; gap: 22px; margin-bottom: 8px; }
.prow .num { font-size: 150px; font-weight: 900; line-height: 1; letter-spacing: -.03em; }
.prow .unit { font-size: 40px; font-weight: 800; color: var(--text-dim); }
.prow.up .num { color: var(--accent); text-shadow: 0 0 70px var(--accent-glow); }
.prow.down .num { color: var(--signal); text-shadow: 0 0 50px rgba(214,137,81,.45); }
.prow .cap { font-size: 26px; font-weight: 700; color: var(--text-dim); align-self: center; }
/* 中间那道"分裂线"——悖论的视觉本体 */
.split {
position: absolute; left: 0; right: 0; top: 540px; height: 3px;
background: linear-gradient(90deg, transparent, var(--accent), var(--signal), transparent);
opacity: .55;
}
/* ---------- A. 大字钩子(画面最亮最大的东西) ---------- */
.hook { position: absolute; left: 130px; right: 130px; top: 600px; }
.hook .kicker {
display: inline-flex; align-items: center; gap: 16px;
font-size: 26px; font-weight: 800; letter-spacing: .30em;
color: var(--accent); margin-bottom: 26px;
}
.hook .kicker .bar { width: 56px; height: 4px; background: var(--accent); }
.hook .big {
font-size: 118px; font-weight: 900; line-height: 1.06; letter-spacing: -.02em;
text-shadow: 0 0 80px rgba(0,0,0,.6);
}
.hook .big em {
font-style: normal; color: var(--accent);
text-shadow: 0 0 60px var(--accent-glow), 0 0 120px var(--accent-glow);
}
/* 角标(配角,不发光) */
.brand {
position: absolute; left: 130px; top: 96px; display: flex; align-items: center; gap: 14px;
font-size: 24px; font-weight: 700; letter-spacing: .18em; color: var(--text-faint);
}
.brand .dot { width: 12px; height: 12px; border-radius: 50%; background: var(--accent); }
</style>
</head>
<body>
<div class="bg"></div>
<div class="grid"></div>
<div class="glow-a"></div>
<div class="glow-b"></div>
<div class="brand"><span class="dot"></span><span data-slot="brand">栏目名 · 期号</span></div>
<!-- ============ B. 核心悖论视觉(换成本期真正的矛盾) ============ -->
<div class="paradox" data-slot="paradox">
<div class="prow up">
<span class="cap">一边</span>
<span class="num">+38%</span>
<span class="unit">↑</span>
</div>
<div class="prow down">
<span class="cap">另一边</span>
<span class="num">-12%</span>
<span class="unit">↓</span>
</div>
</div>
<div class="split"></div>
<!-- ============ A. 大字钩子(≥96px,一句话点破矛盾) ============ -->
<div class="hook" data-slot="hook">
<div class="kicker"><span class="bar"></span><span data-slot="kicker">开场 · 20XX</span></div>
<div class="big" data-slot="big">
同一件事<br>为什么<em>两拨人</em>的感受完全相反
</div>
</div>
<!-- GSAP 本地内置;封面可纯静态,需要入场动画时才用 -->
<script src="../assets/gsap.min.js"></script>
<script>
(function () {
if (!window.gsap) return;
var tl = gsap.timeline({ paused: true });
window.__tl = tl;
// 封面只要"完整态"——所以时间轴总长很短,1.2s 内全部到位,
// cover_build.mjs 默认 seek 到末尾(--at last)取这一帧。
tl.fromTo('.brand', { opacity: 0, y: -18 }, { opacity: 1, y: 0, duration: .5, ease: 'power3.out' }, 0)
.fromTo('.paradox', { opacity: 0, x: 46 }, { opacity: 1, x: 0, duration: .7, ease: 'power3.out' }, .15)
.fromTo('.split', { opacity: 0, scaleX: .2, transformOrigin: 'left center' },
{ opacity: .55, scaleX: 1, duration: .7, ease: 'power3.inOut' }, .35)
.fromTo('.hook .kicker', { opacity: 0, x: -22 }, { opacity: 1, x: 0, duration: .5, ease: 'power3.out' }, .5)
.fromTo('.hook .big', { opacity: 0, y: 30 }, { opacity: 1, y: 0, duration: .6, ease: 'power3.out' }, .62)
.fromTo('.hook .big em', { opacity: 0, scale: .78, transformOrigin: 'center' },
{ opacity: 1, scale: 1, duration: .45, ease: 'back.out(1.8)' }, .95);
if (!window.__MG_RENDER__) {
requestAnimationFrame(function () { requestAnimationFrame(function () { tl.play(0); }); });
}
})();
</script>
</body>
</html>