9b63424740
- src/: HyperFrames 封装 CLI(hv)、组件、品牌样式、音频(TTS/混音)工具 - projects/: 各项目 project.yaml 与文案 - interactive/: 井字棋演示页 - .gitignore: 忽略 node_modules、构建产物与 HyperFrames 运行期缓存
110 lines
3.6 KiB
HTML
110 lines
3.6 KiB
HTML
<!doctype html>
|
|
<html lang="zh-CN"
|
|
data-composition-variables='[{"id": "heading", "type": "string", "label": "标题", "default": "用 **加粗** 和 [[高亮]]"},{"id": "body", "type": "string", "label": "正文(可空)", "default": "支持 **加粗** 与 [[强调色]]"}]'>
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<title>文字块</title>
|
|
<link rel="stylesheet" href="assets/brand/tokens.css" />
|
|
<link rel="stylesheet" href="assets/brand/base.css" />
|
|
<style>
|
|
.stage {
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
}
|
|
|
|
.heading {
|
|
font-size: calc(60 * var(--u));
|
|
font-weight: 700;
|
|
line-height: 1.15;
|
|
letter-spacing: -0.02em;
|
|
margin-bottom: calc(24 * var(--u));
|
|
}
|
|
|
|
.body {
|
|
font-size: calc(30 * var(--u));
|
|
font-weight: 500;
|
|
line-height: 1.55;
|
|
color: var(--text-dim);
|
|
max-width: 82%;
|
|
}
|
|
|
|
/* 逐行浮现:每行一个块 */
|
|
.line { overflow: hidden; }
|
|
.line > span { display: inline-block; }
|
|
|
|
.accent { color: var(--accent); }
|
|
.strong { color: var(--text); font-weight: 700; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div
|
|
class="stage"
|
|
data-composition-id="textblock"
|
|
data-width="1920"
|
|
data-height="1080"
|
|
data-duration="4"
|
|
>
|
|
<div id="heading" class="heading"></div>
|
|
<div id="body" class="body"></div>
|
|
|
|
<script>
|
|
const v = window.hv ? hv.readVars() : null;
|
|
const str = (k, d) => (v ? v.str(k, d) : d);
|
|
|
|
const HEAD = str("heading", "未命名标题");
|
|
const BODY = str("body", "");
|
|
|
|
const headingEl = document.getElementById("heading");
|
|
const bodyEl = document.getElementById("body");
|
|
|
|
/* 支持在文字里用 **强调** 和 [[高亮]] 标记 */
|
|
const rich = (text) => {
|
|
const frag = document.createDocumentFragment();
|
|
const re = /\*\*([^*]+)\*\*|\[\[([^\]]+)\]\]/g;
|
|
let last = 0;
|
|
let m;
|
|
while ((m = re.exec(text)) !== null) {
|
|
if (m.index > last) frag.appendChild(document.createTextNode(text.slice(last, m.index)));
|
|
const el = document.createElement("span");
|
|
if (m[1]) {
|
|
el.className = "strong";
|
|
el.textContent = m[1];
|
|
} else {
|
|
el.className = "accent";
|
|
el.textContent = m[2];
|
|
}
|
|
frag.appendChild(el);
|
|
last = m.index + m[0].length;
|
|
}
|
|
if (last < text.length) frag.appendChild(document.createTextNode(text.slice(last)));
|
|
return frag;
|
|
};
|
|
|
|
headingEl.textContent = "";
|
|
headingEl.appendChild(rich(HEAD));
|
|
|
|
bodyEl.textContent = "";
|
|
if (BODY) {
|
|
const span = document.createElement("span");
|
|
span.appendChild(rich(BODY));
|
|
bodyEl.appendChild(span);
|
|
} else {
|
|
bodyEl.style.display = "none";
|
|
}
|
|
|
|
// 时长跟随宿主(可能由配音决定),拿不到才用组件默认值
|
|
const DUR = (window.hv && hv.duration()) || 4;
|
|
const tl = gsap.timeline({ paused: true });
|
|
|
|
tl.fromTo(headingEl, { opacity: 0, y: 28 }, { opacity: 1, y: 0, duration: 0.5, ease: "power3.out" }, 0.1)
|
|
.fromTo(bodyEl, { opacity: 0, y: 24 }, { opacity: 1, y: 0, duration: 0.5, ease: "power3.out" }, 0.35)
|
|
.to(".stage", { opacity: 0, duration: 0.3, ease: "power1.in" }, DUR - 0.3);
|
|
|
|
window.__timelines = window.__timelines || {};
|
|
window.__timelines["textblock"] = tl;
|
|
tl.seek(0);
|
|
</script>
|
|
</div>
|
|
</body>
|
|
</html> |