[teamai] Push 87 resource(s) from XingfenD
This commit is contained in:
+91
@@ -0,0 +1,91 @@
|
||||
# Anchor: list-reveal(列举型逐个揭示)
|
||||
|
||||
> ⚠️ **这是结构示意,不是抄袭模板**。先走 [`../../CHAPTER-CRAFT.md`](../../CHAPTER-CRAFT.md)
|
||||
> Part 0 五问。本 anchor 给的是"列举型章节的结构骨架"(单网格 N 槽位 +
|
||||
> 每 step 只填一个槽位 + 位置不重排)——保留这个**结构**,**按本项目的
|
||||
> 主题 + 内容换动作选型**。倒过来照抄 = [`../../CHAPTER-CRAFT.md`](../../CHAPTER-CRAFT.md)
|
||||
> Part 5 第 8 条「整章只用一种入场动画」同质化反模式。
|
||||
|
||||
## 定位
|
||||
|
||||
口播说"三件事 / 四个原因 / N 个特性"时,**每项 1 step 逐个揭示**。
|
||||
视频中段最常用的章节类型,**最容易翻车成 PPT** —— 这是为什么需要 anchor。
|
||||
|
||||
## 适用场景
|
||||
|
||||
- "<主体> 强在哪 → 三件事"
|
||||
- "选购 <X> → 四个角度"
|
||||
- "为什么我喜欢 <X> → 五个理由"
|
||||
- 任何"主题 + N 个并列子项"的结构
|
||||
|
||||
## 假设的 outline.md 章节段(抽象)
|
||||
|
||||
```markdown
|
||||
## 4. <chapter-id> — <主题 N 件事>(N+1 steps)
|
||||
|
||||
- **step 1** (~3s) — masthead 引子"<N 件事>"
|
||||
- **step 2** (~6s) — 第 1 件:<标题> + <article 抽来的细节>
|
||||
- **step 3** (~6s) — 第 2 件:<标题>
|
||||
- ...
|
||||
- **step N+1** (~6s) — 第 N 件:<标题>
|
||||
```
|
||||
|
||||
## 关键节奏决策
|
||||
|
||||
| step | 视觉布局 |
|
||||
|---|---|
|
||||
| 1 | 中心引子大字 + 序号 01/02/.../N 占位(**不显示内容**,纯占位) |
|
||||
| 2 | "01" 槽位填充:标题 + 简短说明 + accent 编号;其余仍是 ghost |
|
||||
| 3 | "02" 填充;01 已激活变次级;其余仍 ghost |
|
||||
| ... | 当前槽位填充;之前的激活降级;之后的 ghost |
|
||||
|
||||
## CHAPTER-CRAFT.md Part 0 原则 8 的核心实现
|
||||
|
||||
> "布局不重排,只是单元格内容变化"
|
||||
|
||||
整个章节只有**一个网格布局**,N 个槽位的 React 节点位置完全不变。
|
||||
变的只是每个槽位的内容状态(ghost / active / past)。这样:
|
||||
- 单元格不会重排 → 视觉稳
|
||||
- 每点一次只有"一个槽位变化" → 观众视线明确锁定新揭示的项
|
||||
|
||||
**反模式**:每点一次重新渲染整个布局 → 已揭示的项也跟着抖动 / 重新
|
||||
入场 → 观众不知道该看哪。
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
list-reveal/
|
||||
├── README.md
|
||||
├── chapter.tsx ← 完整章节示例 —— 默认绑 newsroom 主题
|
||||
└── chapter.css
|
||||
```
|
||||
|
||||
## 关键手段(地板线)
|
||||
|
||||
| 维度 | 这个 anchor 怎么实现 |
|
||||
|---|---|
|
||||
| 字号 | 标题 64px / 巨号 144px serif |
|
||||
| 槽位状态 | dashed → 巨号红色高亮 → 灰化数字(**位置不重排**) |
|
||||
| 序号 | hero-num 字体(衬线大数字) |
|
||||
| 主导动作 | mask reveal(标题)+ 数字砸下(accent 红) |
|
||||
| 伴随动作 | 副标 stagger 200ms + accent 横线 scaleX |
|
||||
| 持续微动 | active 槽位的数字 accent 光晕 `infinite` 呼吸 |
|
||||
| 引子 | masthead 双线规则 + serif 大字 |
|
||||
|
||||
> **新写章节时**:抄结构(单网格 N 槽位、每 step 只填一个槽位、位置
|
||||
> 不重排),按本章内容 + 本主题气质自由设计主导动作的形式。
|
||||
|
||||
## 切到其它主题时
|
||||
|
||||
- `bauhaus-bold` → 序号换 Archivo Black + 大色块;用 hard-cut 砸下
|
||||
- `terminal-green` → 序号 `[01]` `[02]` `[03]` 风格;打字机入场
|
||||
- `chalk-garden` → 粉笔下划线手绘 + wiggle 入场
|
||||
- `midnight-press` → 数字 blur clear 慢锐化 + 暖橙光晕慢呼吸
|
||||
|
||||
**结构不变**:N+1 step、单网格 N 槽位、每 step 只填一个槽位。
|
||||
|
||||
## 想看具象题材应用
|
||||
|
||||
- 科技测评 / 实测对比类视频用这个 anchor 长什么样 →
|
||||
[`../case-tech-review/outline-snippet.md`](../case-tech-review/outline-snippet.md)
|
||||
里 `## 2. why-strong` 章节
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
/* ─────────────────────────────────────────────────────────────────
|
||||
* list-reveal · 完整章节示例样式
|
||||
* 默认绑 newsroom 主题。零硬编码视觉属性。
|
||||
* ───────────────────────────────────────────────────────────────── */
|
||||
|
||||
.lr-scene {
|
||||
color: var(--text);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-6);
|
||||
}
|
||||
|
||||
/* ── masthead ── */
|
||||
.lr-masthead {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
.lr-rule {
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: var(--rule);
|
||||
}
|
||||
.lr-kicker {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--t-cue);
|
||||
letter-spacing: 0.18em;
|
||||
color: var(--accent);
|
||||
text-transform: uppercase;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ── intro step ── */
|
||||
.lr-intro {
|
||||
justify-content: center;
|
||||
align-items: stretch;
|
||||
gap: var(--space-5);
|
||||
}
|
||||
.lr-intro-h {
|
||||
margin: 0;
|
||||
font-family: var(--font-display-cn);
|
||||
font-size: var(--t-display-2);
|
||||
text-align: center;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
.lr-em { color: var(--accent); }
|
||||
.lr-intro-sub {
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--t-h3);
|
||||
color: var(--text-2);
|
||||
text-align: center;
|
||||
margin-bottom: var(--space-5);
|
||||
}
|
||||
|
||||
/* ── grid (3 slots, fixed layout) ── */
|
||||
.lr-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: var(--space-5);
|
||||
}
|
||||
|
||||
/* ── slot base ── */
|
||||
.lr-slot {
|
||||
border-radius: var(--r-card);
|
||||
padding: var(--space-5);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
min-height: 360px;
|
||||
transition: opacity var(--dur-base) var(--ease-quart),
|
||||
filter var(--dur-base) var(--ease-quart),
|
||||
border-color var(--dur-base) var(--ease-quart);
|
||||
}
|
||||
|
||||
/* ghost: 虚线边 + 大灰序号 */
|
||||
.lr-slot-ghost {
|
||||
border: var(--rule-w) dashed var(--rule);
|
||||
background: transparent;
|
||||
opacity: 0.55;
|
||||
}
|
||||
.lr-slot-ghost .lr-slot-num {
|
||||
color: var(--text-faint);
|
||||
}
|
||||
|
||||
/* active: 红框 + 实心面板 + 巨号砸下 */
|
||||
.lr-slot-active {
|
||||
border: var(--rule-w) solid var(--accent);
|
||||
background: var(--surface);
|
||||
box-shadow: var(--shadow-card);
|
||||
}
|
||||
.lr-slot-active .lr-slot-num {
|
||||
color: var(--accent);
|
||||
animation: lr-num-drop var(--dur-base) var(--ease-quart) backwards;
|
||||
}
|
||||
@keyframes lr-num-drop {
|
||||
0% { transform: translateY(-40%) scale(1.6); opacity: 0; }
|
||||
60% { transform: translateY(0) scale(0.94); opacity: 1; }
|
||||
100% { transform: translateY(0) scale(1); }
|
||||
}
|
||||
|
||||
/* past: 灰化 */
|
||||
.lr-slot-past {
|
||||
border: var(--rule-w) solid var(--rule);
|
||||
background: transparent;
|
||||
opacity: 0.7;
|
||||
filter: grayscale(0.4);
|
||||
}
|
||||
.lr-slot-past .lr-slot-num {
|
||||
color: var(--text-mute);
|
||||
}
|
||||
|
||||
/* ── slot internals ── */
|
||||
.lr-slot-num {
|
||||
font-family: var(--hero-num-font, var(--font-display-en));
|
||||
font-style: var(--hero-num-style, normal);
|
||||
font-weight: var(--hero-num-weight, 700);
|
||||
font-size: calc(var(--t-display-2) * 0.85);
|
||||
letter-spacing: var(--hero-num-track, -0.02em);
|
||||
line-height: 1;
|
||||
}
|
||||
.lr-slot-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
.lr-slot-title {
|
||||
font-family: var(--font-display-cn);
|
||||
font-size: var(--t-h2);
|
||||
letter-spacing: -0.005em;
|
||||
}
|
||||
.lr-slot-body {
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--t-body);
|
||||
color: var(--text-2);
|
||||
line-height: 1.6;
|
||||
max-width: 22ch;
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
// ⚠️ 这是 anchor 参考代码,不会被任何项目编译。
|
||||
// 抄到真实项目时(presentation/src/chapters/NN-list/),
|
||||
// 把下面两个 import 改成:
|
||||
// import { MaskReveal } from "../../components/MaskReveal";
|
||||
// import type { ChapterStepProps } from "../../registry/types";
|
||||
import { MaskReveal } from "../../../templates/src/components/MaskReveal";
|
||||
import type { ChapterStepProps } from "../../../templates/src/registry/types";
|
||||
import "./chapter.css";
|
||||
|
||||
/**
|
||||
* list-reveal · 完整章节示例
|
||||
* ─────────────────────────────────────────
|
||||
* 默认绑 newsroom 主题。
|
||||
*
|
||||
* 关键手段:
|
||||
* - 槽位用 hero-num(serif 巨号)替代普通文字编号
|
||||
* - 引子用 masthead 双线规则 + serif 大字
|
||||
* - 槽位状态切换有专属动画:
|
||||
* ghost → active:mask reveal 标题 + 数字砸下(accent 红)
|
||||
* active → past :accent 灰化(filter)
|
||||
* - 关键:所有槽位的 React 节点位置不重排,只切换 className
|
||||
*/
|
||||
const ITEMS = [
|
||||
{ num: "01", title: "文字渲染", body: "图里的文字也能正确写出来" },
|
||||
{ num: "02", title: "指令遵循", body: "可以给到非常具体的要求" },
|
||||
{ num: "03", title: "照片真实感", body: "光影 / 材质 / 人物接近真实" },
|
||||
];
|
||||
|
||||
export default function ListRevealChapter({ step }: ChapterStepProps) {
|
||||
// step 1 — 引子
|
||||
if (step === 0) {
|
||||
return (
|
||||
<div className="lr-scene scene-pad lr-intro">
|
||||
<header className="lr-masthead">
|
||||
<span className="lr-rule" />
|
||||
<span className="lr-kicker">第一部分</span>
|
||||
<span className="lr-rule" />
|
||||
</header>
|
||||
<MaskReveal show duration={1100}>
|
||||
<h1 className="lr-intro-h">
|
||||
强在<span className="lr-em">哪</span>
|
||||
</h1>
|
||||
</MaskReveal>
|
||||
<MaskReveal show delay={400} duration={900}>
|
||||
<div className="lr-intro-sub">三件事 —— 一个个看</div>
|
||||
</MaskReveal>
|
||||
|
||||
<div className="lr-grid">
|
||||
{ITEMS.map((it) => (
|
||||
<Slot key={it.num} state="ghost" item={it} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const activeIdx = step - 1;
|
||||
return (
|
||||
<div className="lr-scene scene-pad">
|
||||
<header className="lr-masthead">
|
||||
<span className="lr-rule" />
|
||||
<span className="lr-kicker">第一部分 · 强在哪</span>
|
||||
<span className="lr-rule" />
|
||||
</header>
|
||||
|
||||
<div className="lr-grid">
|
||||
{ITEMS.map((it, i) => {
|
||||
const state =
|
||||
i < activeIdx ? "past" : i === activeIdx ? "active" : "ghost";
|
||||
return <Slot key={it.num} state={state} item={it} />;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Slot({
|
||||
state,
|
||||
item,
|
||||
}: {
|
||||
state: "ghost" | "active" | "past";
|
||||
item: { num: string; title: string; body: string };
|
||||
}) {
|
||||
return (
|
||||
<div className={`lr-slot lr-slot-${state}`}>
|
||||
<div className="lr-slot-num">{item.num}</div>
|
||||
<div className="lr-slot-content">
|
||||
{state !== "ghost" && (
|
||||
<>
|
||||
<MaskReveal show duration={900} key={`${item.num}-title`}>
|
||||
<div className="lr-slot-title">{item.title}</div>
|
||||
</MaskReveal>
|
||||
{state === "active" && (
|
||||
<MaskReveal show delay={350} duration={900}>
|
||||
<div className="lr-slot-body">{item.body}</div>
|
||||
</MaskReveal>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user