[teamai] Push 87 resource(s) from XingfenD

This commit is contained in:
2026-09-10 16:10:45 +08:00
parent 425c9c078a
commit 65c04def51
1314 changed files with 211681 additions and 0 deletions
@@ -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` 章节
@@ -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;
}
@@ -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>
);
}