# Babao Lab：UI 与动效实现手册（修订版）

更新：2026-09-08。此版替代早期“只能参照截图重建”的说明。

## 已经拿到了什么

你提供的 /Users/babaozhou/Downloads/field-notes/scrape-makingsoftware 中包含原站渲染后的 HTML 快照、main.css、extract.json 与 tokens.json。我们读取了这些本地文件，并未破解 Vercel。两个带哈希名称的 CSS 文件实际上是 Vercel Security Checkpoint 页面，不能当作有效样式表。当前也没有获得原站 React/TypeScript 项目源码或完整 JavaScript 逻辑。

因此可以可靠核验 SVG 路径、内联 SMIL、渲染时使用的样式类、字体名称和颜色值；不能声称拿到了完整原始工程，也不能证明快照与目前线上版本完全相同。

截图中的作者回答明确说明：插图由他在 Figma 中手工绘制。出处：[Making Software](https://www.makingsoftware.com/)，Dan Hollick。

用户提供的源目录保持原样。只把选定字体和经过处理的 SVG 图形加入 Babao Lab。

## 字体和颜色：哪些一致，哪些仍是回退

| 项目 | 快照证据 | 本站实现 |
| --- | --- | --- |
| 点阵 / 等宽字体 | departureMono，变量 --font-departure-mono | 使用提供目录中的 DepartureMono-Regular.woff2；保留 OFL |
| 正文衬线字体 | arizona, arizona Fallback, EB Garamond, serif | Arizona 文件未提供；本地加载 EB Garamond 回退字体。不能称为 Arizona 原字体 |
| 中文正文 | 原站没有中文版本 | 宋体系统回退，避免用拉丁字体代替中文字形 |
| 背景 | #fbfbfb | #fbfbfb |
| 蓝色主色 | oklch(50.58% .2886 264.84) | 对应 --blue / --color-cobalt-600 |
| 蓝色阶梯 | cobalt-50 至 cobalt-700 | 保留提取的 OKLCH 数值 |

标题现用 Departure Mono 字体，替换上一版自绘 5×7 字库。目录标题、图形标注、按钮、进度数字和页脚也使用这套字体。正文不是精确的 Arizona；日后获得适用字体文件后，可替换 @font-face 与正文 font-family。

## 页面结构与双语

三个主体板块为 Agentic Blog、生活产品、材料科学。首页按参考设计排列：像素字标 → 图文交错导读 → CRT 宽图 → 三栏目录 → 阅读进度 → 硬盘 → 模糊问答 → 按键结构图与材料图 → 页脚。

components/language.tsx 保存中文 / English 选择，使用 useSyncExternalStore 同步组件。用户选择存入本浏览器 localStorage，切换页面后沿用。document.lang 同步为 zh-CN 或 en。首页文字、目录、问答、控制按钮、实验台和下载入口均有两种语言。参考 SVG 内已经转为路径的英文标签保留原样，外侧图注提供双语。

目录内容是实际的主题导读，不是已经发布的独立长文。将来可以把条目改为真实文章链接。

## 模糊滚动选择器

代码：components/focus-questions.tsx；样式：globals.css 中 focus-*。

已从原站 HTML 中核实的规则：

- distance = abs(itemIndex - activeIndex)
- 水平位移 = distance × -2px
- 缩放 = 1 - distance × 0.05
- 不透明度 = 1 - distance × 0.15
- 模糊半径 = distance × 1.5px
- 旋转根据相对索引变化，每档约 1°
- 过渡约 300ms；悬停时解除模糊、缩放和透明度

本站新增的交互：

1. 左侧滚轮：累积滚动量超过阈值后移动一项，短暂节流避免触控板一次跳很多项。
2. 点击某个问题直接选择；上下箭头同样可操作。
3. 方向键、Home、End 配合 roving tabIndex；问题使用 tab，答案使用 tabpanel。
4. 触屏上下滑动；该小区域作为选择器处理垂直手势。
5. 选中项始终移动到窗口焦点，边缘通过 mask-image 渐隐。
6. 到首尾时滚轮不再拦截，允许继续滚动网页。
7. 右侧采用 IN / OUT 边框排版，答案通过轻微 blur + clip-path 入场。

这部分借鉴并改进了你提供的 js/site.js 和 css/site.css。其视觉公式可用快照核验；滚轮、触屏与键盘逻辑是本站实现，不是声称读到了原站 JavaScript。

调节 --faq-blur 可以改变景深强度。修改 48px 行高时，也要同步调整焦点位移和箭头位置。

## 点阵目录与进度条

目录每行采用“标题 / 自动扩展的点状引导线 / 数值”的三列布局。右上角切换 PROGRESS / WORDS。

- 字数模式：从实际主题导读计算；中文是去空白后的字符数（含标点），英文按词计算。
- 进度模式：显示本次打开过的导读。
- 下方进度：已打开数量 / 11；初次进入为 0。
- 数字不是作者的写作进度、文章发布进度或科研完成率。
- 阅读状态保存在当前页面 React 状态中；完整刷新后重置。

进度条用蓝色实心 div 覆盖点阵背景；尾部露出的底图保留参考设计的颗粒效果。另有原生 progress 提供可访问数值。数值改变时只对宽度做过渡，减少动态效果模式下立即更新。

## 图形从哪里来

components/reference-svg.json 保存从用户提供的 HTML 中提取的 8 个内联 SVG：

| 名称 | 原快照 SVG 索引 | 核验到的动画元素数 |
| --- | --- | --- |
| floppy | 2 | 23 |
| pixels | 3 | 0 |
| gaussian | 4 | 0 |
| bezier | 5 | 10 |
| raster | 6 | 6 |
| crt | 10 | 6 |
| drive | 42 | 2 |
| keyswitch | 43 | 12 |

计数包括 animate、animateTransform 等以 animate 开头的元素。没有动画的原图保持静态，不为所有图形强行增加循环。

提取时移除脚本、foreignObject、事件属性和非片段 href，对 SVG 做 XML 解析校验，再为各图的 ID 加前缀以避免定义冲突。组件只插入这些本地固定内容，不接受用户或远程传入的 HTML。不要在未来把未经处理的外部字符串直接放进 dangerouslySetInnerHTML。

源快照与图形作者保留在页脚说明中。完整原站正文、账号界面和脚本没有复制进网站。

## SMIL 动画如何工作

参考 SVG 里的动画不是 GIF。例如软盘使用 animateTransform，直接给分组的 transform 指定时间序列：

```xml
<g transform="translate(158,-240)">
  <animateTransform
    attributeName="transform"
    type="translate"
    dur="1.5s"
    begin="1.5s"
    values="158,-240; 0,-140; 0,0"
    keyTimes="0;0.33;1"
    calcMode="spline"
    keySplines="0.25 0.1 0.25 1;0.25 0.1 0.25 1"
    fill="freeze" />
  <!-- 零件路径 -->
</g>
```

- values：关键位置。
- keyTimes：每个位置在总时长中的比例。
- keySplines：相邻位置之间的缓动。
- begin：延迟。
- fill="freeze"：动画结束保留最终形态。
- repeatCount="indefinite"：需要循环时使用。
- animateMotion / mpath：可以沿已有路径移动。

components/reference-figure.tsx 用 SVG 自身的 pauseAnimations、unpauseAnimations 和 setCurrentTime 控制暂停 / 重播。单靠 CSS animation-play-state 不能暂停 SMIL。

IntersectionObserver 在图形离屏时暂停，进入视口后继续。prefers-reduced-motion 为真时跳到稳定画面并暂停。页眉暂停按钮同时控制 SMIL 和自定义 CSS 动画。

详见 [MDN：SVG animation with SMIL](https://developer.mozilla.org/en-US/docs/Web/SVG/Guides/SVG_animation_with_SMIL)。

## 每幅图的蓝色点点背景

.plate 与 .studio-preview 统一使用径向渐变平铺：

```css
background-image: radial-gradient(
  circle,
  color-mix(in srgb, var(--blue) 18%, transparent) .55px,
  transparent .7px
);
background-size: var(--dot-size) var(--dot-size);
```

--dot-size 默认 3px。缩小数值使点更密；调整 18% 改变点的可见度；调整半径改变点大小。图形底色用不透明的浅色填充，遮住零件内部的点阵；背景仍从外侧空白处透出。

图注有纸色底，覆盖背景点阵。页脚使用两层不同间距点阵再叠渐变 mask，形成由浅到密的颗粒带。

## 原图与可定制组件的分工

components/blueprints.tsx 保留上一版的参数化组件，用于图形实验台以及材料晶格。

- ExplodedDisk：React 状态 + CSS transform，适合学习合拢 / 展开交互。
- Gaussian：按数学函数生成网格 path，spread 控制 σ。
- Crystal：节点阵列 + 分层投影 + offset-path，层间距与迁移路径同步调整。
- 首页的精细软盘 / 硬盘 / 按键等：使用参考 SVG 的原始路径和内联动画。

实验台默认不改写首页，也不会保存到服务器。导出 SVG 是静态当前帧；导出 JSON 是参数文件。若需要保留动画，应复用组件 + CSS，或直接编辑参考 SVG 内的 SMIL 属性。

## 以后怎样做自己的图案和动画

1. 先画清楚结构。复杂插图可在 Figma 中制作；数学图形适合程序生成。
2. 按“一起运动的零件”分组，如 cap、shaft、spring、base。
3. 给重要分组和路径加稳定名称。
4. 决定动画类型：CSS 适合循环 / 状态过渡，SMIL 适合独立 SVG 时序，React 状态适合交互，复杂物理或逐帧数据才考虑 requestAnimationFrame。
5. 先调终态，再调起点和时长。分层动画可用 begin 错开。
6. 不要在同一分组上同时让 CSS 和 SMIL 控制同一 transform；嵌套分组分离职责。
7. 颜色集中在 CSS 变量中；图形缩放用 viewBox。
8. 测试暂停、减少动效、键盘与手机，不把关键信息只放在运动中。

## 文件索引

- app/page.tsx：双语首页、主题数据、目录和阅读进度。
- app/studio/page.tsx：双语图形实验台与导出逻辑。
- components/language.tsx：语言状态。
- components/focus-questions.tsx：模糊焦点选择器。
- components/reference-figure.tsx：SMIL 播放器与节能控制。
- components/reference-svg.json：精细参考 SVG。
- components/blueprints.tsx：自定义图形与参数。
- app/globals.css：版式、字体、点阵、图注、过渡。
- public/fonts：本地字体与许可。

## 验证边界

工程构建和变更代码检查用于排除编译、渲染和静态代码问题，不等价于逐像素验收。当前没有对原站实时版本做逐帧比较。本站的字体回退、中文排版、内容重组和新增交互也意味着它不是原网页的完整镜像。

这份快照已经解决了原先缺少图形路径和样式证据的问题；不需要通过绕过防护来继续复刻。
