# 第三章：架构重构与对比分析 (/docs/v4/03)

> 本章聚焦 **CDS 3.0 中被 `open-claude-ui` 误实现或完全未实现的机制**。每节先给 CSS 原文，再给结论，最后给与当前实现（`feat/issue-25-skeleton-sweep-and-rea-audit` 分支）的差异。

---

## 3.1 骨架屏：双层组件结构，sweep band 是独立子元素 [#31-骨架屏双层组件结构sweep-band-是独立子元素]

### 3.1.1 真实的 DOM 层级 [#311-真实的-dom-层级]

CDS 3.0 的骨架屏由**两层 `data-cds` 属性**构成：

```
[data-cds="SkeletonGroup"]   ← 组容器，控制 motion / hold
  └─ [data-cds="Skeleton"]   ← 单个骨架块
       └─ .cds-skel-sweep-clip    ← 裁剪层（overflow:hidden）
            └─ .cds-skel-sweep-band  ← 扫光光带（真正动的元素）
```

**证据（CSS 原文）**

```css
/* 裁剪层 */
.cds-root .cds-skel-sweep-clip {
  pointer-events: none;
  position: absolute;
  inset: 0;
  overflow: hidden
}

/* 光带 */
.cds-root .cds-skel-sweep-band {
  background: linear-gradient(105deg, transparent 35%, var(--cds-skeleton-sheen), transparent 65%);
  animation: cds-skeleton-sweep 2s linear var(--cds-skel-reveal-end, 0s) infinite backwards;
  position: absolute;
  inset: 0
}
```

**关键点**

| 观察 | 说明 |
| :--- | :--- |
| `.cds-skel-sweep-band` 是**真实 DOM 元素** | 不是 `::after` 伪元素 |
| `.cds-skel-sweep-clip` 提供 `overflow:hidden` | 扫光被裁剪在骨架块内 |
| `position:absolute; inset:0` 属于 **band / clip** | 不属于骨架块本身 |
| 骨架块 `[data-cds="Skeleton"]` 本身**没有** `position:absolute` 规则 | 它保持正常文档流 |

### 3.1.2 与 issue #25 分支的差异 [#312-与-issue-25-分支的差异]

分支的实现（`cds-theme.css`）：

```css
.cds-skeleton-sweep,
.cds-skeleton[data-motion="wave"]::after,
.cds-skeleton:not([data-motion])::after {
  content: "";
  position: absolute;      /* ← 问题所在 */
  inset: 0;
  transform: translate(-100%);
  …
}
```

**三个结构性偏差**：

| 项 | CDS 3.0 实际 | 分支实现 | 后果 |
| :--- | :--- | :--- | :--- |
| 光带载体 | 独立元素 `.cds-skel-sweep-band` | `::after` 伪元素 | 伪元素无法被 JS 查询/替换 |
| `.cds-skeleton-sweep` 作为独立类 | **不存在此语义** | 写在同一条规则的选择器列表里 | 该类的元素自身变成 `position:absolute` + `translate(-100%)`，被推出视口 |
| 裁剪机制 | 独立 `.cds-skel-sweep-clip` + `overflow:hidden` | 依赖骨架块自身 `overflow:hidden` | 结构上可工作，但缺少显式裁剪层 |

**分支缺陷的实测证据**（Chrome headless，用 storybook 自己的 CSS）：

```
class="cds-skeleton cds-skeleton-sweep h-4 rounded w-3/4" data-motion="wave"
→ position: absolute, x = -245, 渲染为不可见
```

这正是 3.1.2 表格第二行描述的直接后果。

### 3.1.3 修复方向 [#313-修复方向]

**最小改动**（保留现有类名，去掉并列的独立类选择器）：

```css
/* 只保留伪元素形态，不把 .cds-skeleton-sweep 放进这条规则 */
.cds-skeleton[data-motion="wave"]::after,
.cds-skeleton:not([data-motion])::after { … }
```

**完整对齐**（推荐，需同步改组件标记）：

```css
.cds-skeleton { position: relative; overflow: hidden; }
.cds-skel-sweep-clip { pointer-events:none; position:absolute; inset:0; overflow:hidden; }
.cds-skel-sweep-band {
  background: linear-gradient(105deg, transparent 35%, var(--cds-skeleton-sheen), transparent 65%);
  animation: cds-skeleton-sweep 2s linear var(--cds-skel-reveal-end, 0s) infinite backwards;
  position: absolute; inset: 0;
}
```

---

## 3.2 骨架屏：`data-hold` 三态揭示时序 [#32-骨架屏data-hold-三态揭示时序]

这是 **CDS 3.0 完全新增的机制**，v2 文档与 issue #25 分支均未涉及。

### 3.2.1 三个状态 [#321-三个状态]

`data-hold` 属性（挂在 `SkeletonGroup` 上）有三个取值：

| 取值 | CSS 行为 | 语义 |
| :--- | :--- | :--- |
| **缺省**（无该属性） | 使用 `var(--placeholder-reveal-delay, .5s)` 的默认揭示时序 | 标准延迟揭示 |
| **`none`** | `--cds-skel-reveal-end: calc(0s - var(--cds-skel-elapsed))` | 立即开始，不等待 |
| **`delayed`** | 额外挂 `cds-placeholder-reveal .15s ease-out …backwards` 动画 | 显式延迟揭示 |

### 3.2.2 CSS 原文 [#322-css-原文]

```css
/* 基础（无 data-hold 或 data-hold 无值时） */
.cds-root [data-cds=SkeletonGroup][data-hold] {
  --cds-skel-elapsed: 0s;
  --cds-skel-i: 0;
  --cds-skel-settled: 0s;
  --cds-skel-reveal-end: calc(var(--placeholder-reveal-delay, .5s) + .15s - var(--cds-skel-elapsed))
}

/* hold=none：立即 */
.cds-root [data-cds=SkeletonGroup][data-hold=none] {
  --cds-skel-reveal-end: calc(0s - var(--cds-skel-elapsed))
}

/* hold=delayed：显式延迟 + 淡入 */
.cds-root [data-cds=SkeletonGroup][data-hold=delayed] {
  animation: cds-placeholder-reveal .15s ease-out
             calc(var(--placeholder-reveal-delay, .5s) - var(--cds-skel-elapsed) - var(--cds-skel-settled))
             backwards
}
```

### 3.2.3 揭示延迟公式 [#323-揭示延迟公式]

从上面三段可推出完整的 `--cds-skel-reveal-end` 取值链：

```
data-hold 缺省 / 有值但非 none 非 delayed:
  reveal-end = placeholder-reveal-delay(.5s) + 0.15s - elapsed

data-hold = none:
  reveal-end = 0s - elapsed

data-hold = delayed:
  reveal-end = (同上缺省公式)
  额外叠加 cds-placeholder-reveal 动画，延时 = reveal-delay - elapsed - settled
```

**含义**：`.5s` 是默认的占位揭示延迟，`+0.15s` 是 `cds-placeholder-reveal` 动画自身时长（`.15s ease-out`）。`elapsed` / `settled` 由 JS 在运行时写入，用于「组件已经挂载了多久」的补偿——避免骨架屏在已经加载完成后才启动动画。

> **⚠️ 不可解部分**：`--cds-skel-elapsed` 与 `--cds-skel-settled` 的默认值在 CSS 里是 `0s`，但它们在运行时由谁写入、写入什么值，静态分析无法确定（需运行时观测）。本文只记录 CSS 侧的公式。

### 3.2.4 揭示完成后停止动画 [#324-揭示完成后停止动画]

```css
/* 一旦 data-hold 且 data-motion 同时存在（即进入揭示完成态） */
.cds-root [data-cds=SkeletonGroup][data-hold][data-motion],
.cds-root [data-cds=SkeletonGroup][data-hold][data-motion] [data-cds=Skeleton] {
  animation-duration: .01ms;
  animation-iteration-count: 1
}

/* 并隐藏扫光带 */
.cds-root [data-cds=SkeletonGroup][data-hold][data-motion] .cds-skel-sweep-band {
  display: none
}
```

**这是 CDS 3.0 的「动画收尾」机制**：当骨架内容已揭示完成，把动画压到 `.01ms` 单次执行（实质是立即结束），并直接 `display:none` 掉扫光带。

**分支完全缺失此机制**——分支的骨架屏会无限循环扫光，不会在内容到达后停止。

---

## 3.3 骨架屏：双模 wave / lockstep 的真实延时公式 [#33-骨架屏双模-wave--lockstep-的真实延时公式]

### 3.3.1 wave 模式（波浪级联） [#331-wave-模式波浪级联]

```css
.cds-root [data-cds=SkeletonGroup][data-motion=wave] [data-cds=Skeleton] {
  animation: cds-skeleton-breathe 2s ease-in-out
             calc(var(--cds-skel-reveal-end, 0s) + var(--cds-skel-i, 0) * 90ms)
             infinite
}
```

**关键发现**：wave 模式用的是 **`cds-skeleton-breathe`（透明度呼吸）**，不是扫光 sweep。

```
动画: cds-skeleton-breathe
时长: 2s
缓动: ease-in-out
延时: reveal-end + (skel-i × 90ms)
迭代: infinite
```

**这与分支的实现相反**：分支把 wave 模式绑定到 `cds-skeleton-sweep`（扫光），而实测中用 breathe。

### 3.3.2 lockstep 模式（同步呼吸） [#332-lockstep-模式同步呼吸]

```css
/* hold=none 时 */
.cds-root [data-cds=SkeletonGroup][data-motion=lockstep][data-hold=none] {
  animation: cds-skeleton-breathe 2s ease-in-out var(--cds-skel-reveal-end) infinite
}

/* hold=delayed 时：叠加揭示动画 */
.cds-root [data-cds=SkeletonGroup][data-motion=lockstep][data-hold=delayed] {
  animation: cds-skeleton-breathe 2s ease-in-out var(--cds-skel-reveal-end) infinite,
             cds-placeholder-reveal .15s ease-out
             calc(var(--placeholder-reveal-delay, .5s) - var(--cds-skel-elapsed) - var(--cds-skel-settled))
             backwards
}
```

**注意**：lockstep 的动画挂在 **`SkeletonGroup` 容器**上，作用于整个组；wave 的动画挂在**每个 `Skeleton` 子块**上，带级联延时。这是两种模式的本质区别。

### 3.3.3 两种模式的对比表 [#333-两种模式的对比表]

| 维度 | `data-motion="wave"` | `data-motion="lockstep"` |
| :--- | :--- | :--- |
| 动画 | `cds-skeleton-breathe` 2s ease-in-out | 同左 |
| 挂载位置 | 每个 `[data-cds=Skeleton]` 子块 | `[data-cds=SkeletonGroup]` 容器 |
| 延时 | `reveal-end + skel-i × 90ms` | `reveal-end`（无级联） |
| 效果 | 逐个错峰呼吸 | 整组同步呼吸 |
| 适用 | 大卡片、多块布局 | 密集列表 |

### 3.3.4 与分支的差异汇总 [#334-与分支的差异汇总]

| 项 | CDS 3.0 | 分支 | 判定 |
| :--- | :--- | :--- | :--- |
| wave 动画 | `cds-skeleton-breathe`（呼吸） | `cds-skeleton-sweep`（扫光） | ❌ 用错动画 |
| lockstep 挂载 | Group 容器 | `.cds-skeleton[data-motion="lockstep"]`（每个块） | ⚠️ 语义不同但效果接近 |
| 级联延时 | `reveal-end + i×90ms` | `i × 90ms` | ⚠️ 缺 reveal-end 项 |
| 扫光触发 | 由 `.cds-skel-sweep-band` 独立元素承担，与 motion 无关 | 绑定到 data-motion | ❌ 混淆了两种机制 |

**核心误解**：分支把「扫光」和「呼吸」当成了 wave / lockstep 两种模式。**实际是两个正交的机制**：

- **扫光**（`.cds-skel-sweep-band`）：独立元素，`2s linear`，与 data-motion 无关
- **呼吸**（`cds-skeleton-breathe`）：由 data-motion 的 wave/lockstep 驱动，`2s ease-in-out`

---

## 3.4 骨架屏：RTL 语言级适配 [#34-骨架屏rtl-语言级适配]

### 3.4.1 CSS 原文 [#341-css-原文]

```css
/* 扫光带：RTL 时渐变角度 105° → 255°，并反转动画方向 */
.cds-root .cds-skel-sweep-clip:where(
  :root[dir=rtl] :is(
    :lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb),
    :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn),
    :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)
  )
) .cds-skel-sweep-band {
  background-image: linear-gradient(255deg, transparent 35%, var(--cds-skeleton-sheen), transparent 65%);
  animation-direction: reverse
}

/* 骨架块自身的伪元素同样反转 */
.cds-root [data-cds=Skeleton][data-cds=Skeleton]:where(
  :root[dir=rtl] :is(…同上 19 个语言…)
):after {
  animation-direction: reverse
}
```

### 3.4.2 三个关键点 [#342-三个关键点]

| 点 | 说明 |
| :--- | :--- |
| **`:lang()` 精确语言列表** | 19 个语言代码：`ae ar arc bcc bqi ckb dv fa glk he ku mzn nqo pnb ps sd ug ur yi` |
| **`animation-direction: reverse`** | 不只是翻转渐变角度，还要反转动画播放方向，扫光才会从右往左 |
| **`[data-cds=Skeleton][data-cds=Skeleton]` 重复属性** | 这是提高选择器特异性的技巧，用于压过其它规则 |

### 3.4.3 与分支的差异 [#343-与分支的差异]

| 项 | CDS 3.0 | 分支 | 判定 |
| :--- | :--- | :--- | :--- |
| RTL 判定 | `:root[dir=rtl]` **且** `:lang(<19 个语言之一>)` | 仅 `:dir(rtl)` / `[dir="rtl"]` | ⚠️ 范围不同 |
| 渐变角度翻转 | ✅ 105° → 255° | ✅ 105° → 255° | ✅ 一致 |
| 动画方向反转 | ✅ `animation-direction: reverse` | ❌ **缺失** | ❌ 分支的 RTL 仍然从左往右扫 |

**分支的 RTL 实测**（Chrome，`<html dir="rtl">`）：

```
LTR: transform = matrix(1,0,0,1,-220,0)
RTL: transform = matrix(1,0,0,1,-220,0)   ← 完全相同，未反转
```

---

## 3.5 减少动效与残留动效兜底 [#35-减少动效与残留动效兜底]

### 3.5.1 三层兜底机制 [#351-三层兜底机制]

CDS 3.0 对减少动效（reduced motion）有三层处理：

```css
/* 第 1 层：属性开关 —— data-reduce-motion=true 时也要收尾 */
.cds-root:where([data-reduce-motion=true],[data-reduce-motion=true] *)
  [data-cds=SkeletonGroup][data-hold][data-motion],
.cds-root:where([data-reduce-motion=true],[data-reduce-motion=true] *)
  [data-cds=SkeletonGroup][data-hold][data-motion] [data-cds=Skeleton] {
  animation-duration: .01ms;
  animation-iteration-count: 1
}

.cds-root:where([data-reduce-motion=true],[data-reduce-motion=true] *)
  [data-cds=SkeletonGroup][data-hold][data-motion] .cds-skel-sweep-band {
  display: none
}
```

**第 2 / 3 层**（在其它组件上，抽样证据）：

```css
/* chip 进入动画：媒体查询层 */
@media (prefers-reduced-motion: reduce) {
  .cds-root .cds-chip-in { animation: none }
}

/* chip 进入动画：属性层 */
.cds-root:where([data-reduce-motion=true],[data-reduce-motion=true] *) .cds-chip-in {
  animation: none
}

/* flipbook：媒体查询层 */
@media (prefers-reduced-motion: reduce) {
  .cds-root .cds-flipbook { animation: none }
}

/* flipbook：属性层 */
.cds-root:where([data-reduce-motion=true],[data-reduce-motion=true] *) .cds-flipbook {
  animation: none
}
```

### 3.5.2 双轨制 [#352-双轨制]

CDS 3.0 的减少动效**同时监听两种来源**：

| 来源 | 选择器 | 层级 |
| :--- | :--- | :--- |
| 系统偏好 | `@media (prefers-reduced-motion: reduce)` | 媒体查询 |
| 应用开关 | `[data-reduce-motion=true]` | 属性 |

两者**并列**，各自独立生效。这是为 Electron 应用设计的：系统偏好可能不可靠（或用户想单独控制某个应用），所以提供应用级显式开关。

### 3.5.3 与分支的差异 [#353-与分支的差异]

| 项 | CDS 3.0 | 分支 |
| :--- | :--- | :--- |
| `@media (prefers-reduced-motion)` | ✅ 多处使用 | ⚠️ 仅 1 处，且只针对 `[data-cds-anim]` |
| `[data-reduce-motion=true]` 属性开关 | ✅ 全组件覆盖 | ❌ **完全缺失** |
| 骨架屏动效兜底 | ✅ 有 | ❌ 无 |

**结论**：分支的骨架屏动画**在任何情况下都无限循环**，不响应系统的减少动效设置。

---

## 3.6 Pill 胶囊：1.32x 过冲的 9 个关键帧 [#36-pill-胶囊132x-过冲的-9-个关键帧]

v2 文档提到「Pill 1.32x 过冲回弹」，但未给出关键帧定义。以下是 3.6556.2 的原文。

### 3.6.1 四个关键帧 [#361-四个关键帧]

```css
@keyframes cds-pill-in       { 0%   { transform: scale(.8)   } }
@keyframes cds-pill-pop-up   { to   { transform: scale(1.32) } }
@keyframes cds-pill-pop-down { 0%   { transform: scale(1.32) } }
@keyframes cds-pill-dip-up   { 0%   { transform: scale(.82)  } }
```

### 3.6.2 命名规律与数值 [#362-命名规律与数值]

| 关键帧 | 关键值 | 用途推断（基于命名与数值） |
| :--- | :--- | :--- |
| `cds-pill-in` | `scale(.8)` 起点 | 入场 |
| `cds-pill-pop-up` | 终点 `scale(1.32)` | 上弹到 1.32x |
| `cds-pill-pop-down` | 起点 `scale(1.32)` | 从 1.32x 回落 |
| `cds-pill-dip-up` | 起点 `scale(.82)` | 下压后回升 |

**1.32 与 0.82 的对称性**：

```
1.32 - 1.00 = +0.32
1.00 - 0.82 = +0.18
```

不是严格对称。`.82` 是压入，`1.32` 是弹出，弹比压更夸张——符合「过冲手感」的意图。

### 3.6.3 完整的 pill 关键帧家族 [#363-完整的-pill-关键帧家族]

3.6556.2 中与 pill 相关的关键帧共 **9 个**：

```
cds-pill-dip-down
cds-pill-dip-up
cds-pill-in
cds-pill-out
cds-pill-pop-down
cds-pill-pop-up
cds-pill-roll-in-down
cds-pill-roll-in-up
cds-pill-roll-out
```

分为三个系列：

| 系列 | 成员 | 数值特征 |
| :--- | :--- | :--- |
| `in` / `out` | `cds-pill-in`、`cds-pill-out` | 入场/退场 |
| `pop` | `cds-pill-pop-up`、`cds-pill-pop-down` | `scale(1.32)` 过冲 |
| `dip` | `cds-pill-dip-up`、`cds-pill-dip-down` | `scale(.82)` 压入 |
| `roll` | `cds-pill-roll-in-down`、`cds-pill-roll-in-up`、`cds-pill-roll-out` | 滚动进出 |

**证据**：`grep -o "@keyframes cds-pill-[a-z-]*" MainWindowPage-BjVtjJFx.css | sed 's/@keyframes //' | sort -u | wc -l` → **9**

**分支与 open-claude-ui 均无这 9 个关键帧中的任何一个。** open-claude-ui 的 `ClaudeMotionPresets.ts` 用 Framer Motion spring 实现类似效果，但数值体系（`stiffness/damping/mass`）与 CSS keyframes（`scale` 离散值）不是同一套。

---

## 3.7 滚动渐隐：`@property` 驱动的纯 CSS 实现 [#37-滚动渐隐property-驱动的纯-css-实现]

### 3.7.1 `@property` 注册 [#371-property-注册]

CDS 3.0 用 `@property` 注册了一批可动画的自定义属性。与滚动渐隐相关的 **17 个**：

```css
@property --cds-scroll-fade-top          { … }
@property --cds-scroll-fade-top-on       { … }
@property --cds-scroll-fade-top-raw      { … }
@property --cds-scroll-fade-bottom       { … }
@property --cds-scroll-fade-bottom-on    { … }
@property --cds-scroll-fade-bottom-raw   { … }
@property --cds-scroll-fade-left         { … }
@property --cds-scroll-fade-left-on      { … }
@property --cds-scroll-fade-left-raw     { … }
@property --cds-scroll-fade-right        { … }
@property --cds-scroll-fade-right-on     { … }
@property --cds-scroll-fade-right-raw    { … }
@property --cds-scroll-fade-size         { … }
@property --cds-scroll-fade-x-active     { … }
@property --cds-scroll-fade-y-active     { … }
@property --cds-scroll-fade-strip-active { … }
@property --cds-scroll-fade-strip-raw    { … }
```

**证据**：`grep -o "@property --cds-scroll-fade-[a-zA-Z0-9_-]*" MainWindowPage-BjVtjJFx.css | sort -u | wc -l` → **17**

另有 3 个与吸边（sticky edge）相关的 `@property`：`--cds-sticky-edge-active` / `--cds-sticky-edge-end` / `--cds-sticky-edge-start`。

### 3.7.2 默认值 [#372-默认值]

```css
.cds-root {
  --cds-scroll-fade-size: 28px;
  --cds-scroll-fade-top: 0px;
  --cds-scroll-fade-bottom: 0px;
  --cds-scroll-fade-left: 0px;
  --cds-scroll-fade-right: 0px;
  --cds-scroll-fade-top-raw: 0px;
  --cds-scroll-fade-bottom-raw: 0px;
  --cds-scroll-fade-left-raw: 0px;
  --cds-scroll-fade-right-raw: 0px;
  --cds-scroll-fade-y-active: 0;
  --cds-scroll-fade-x-active: 0;
  --cds-scroll-fade-top-on: 1;
  --cds-scroll-fade-bottom-on: 1;
  --cds-scroll-fade-left-on: 1;
  --cds-scroll-fade-right-on: 1;
  --cds-scroll-fade-strip-raw: 0;
  --cds-scroll-fade-strip-active: 0
}
```

### 3.7.3 滚动驱动 [#373-滚动驱动]

```css
animation-timeline: scroll()
```

出现 1 处（用于 `[data-cds-title-leaves]` 的页面标题动画）。

### 3.7.4 与 v2 文档 / 分支的差异 [#374-与-v2-文档--分支的差异]

| 项 | CDS 3.0 | v2 文档声称 | open-claude-ui 现状 |
| :--- | :--- | :--- | :--- |
| 实现方式 | `@property` + `animation-timeline: scroll()` | 「7 停非线性 mask」 | `@property` + `animation-timeline` |
| 渐隐尺寸 | `--cds-scroll-fade-size: 28px` | 未记录 | 已实现 |
| mask-image | `linear-gradient(#000 0 0)`（纯色占位，由 `@property` 变量控制） | 「7 停 mask」 | 待核对 |

> **⚠️ 需注意**：本文在 3.6556.2 主样式表中只找到 `mask-image:linear-gradient(#000 0 0)` 这一处，**未找到 v2 文档描述的「7 停非线性 mask」原文**。这可能是：  
> 1. 该实现已重构为 `@property` 变量驱动  
> 2. 该实现位于其它 CSS 文件（如 `window-shared.css`）  
> 3. v2 文档的「7 停」描述本身是推断而非取证
>
> 本文**不对「7 停 mask 是否仍然存在」下结论**，标注为 `unknown`，需进一步核对 `window-shared.css` 与运行时计算样式。

---

## 本章证据索引 [#本章证据索引]

| 断言 | 证据 |
| :--- | :--- |
| 骨架屏双层 `data-cds` | CSS 选择器 `[data-cds=SkeletonGroup]` / `[data-cds=Skeleton]` 原文 |
| `.cds-skel-sweep-band` 是真实元素 | CSS `.cds-root .cds-skel-sweep-band { … }` 无 `::after` |
| `data-hold` 三态 | 三段 `[data-hold]` / `[data-hold=none]` / `[data-hold=delayed]` 规则 |
| wave 用 breathe 不用 sweep | `.cds-root [data-cds=SkeletonGroup][data-motion=wave] [data-cds=Skeleton] { animation: cds-skeleton-breathe … }` |
| lockstep 挂 Group | `[data-cds=SkeletonGroup][data-motion=lockstep]…` 选择器层级 |
| 90ms 级联 | `calc(var(--cds-skel-reveal-end,0s) + var(--cds-skel-i,0) * 90ms)` |
| RTL 19 个语言 + reverse | `:lang(ae,ar,arc,…)` 完整列表 + `animation-direction:reverse` |
| 减少动效双轨 | `@media (prefers-reduced-motion:reduce)` 与 `[data-reduce-motion=true]` 并列出现 |
| pill 1.32x | `@keyframes cds-pill-pop-up { to { transform: scale(1.32) } }` |
| `@property` 滚动渐隐 17 项 | `@property --cds-scroll-fade-*` 清单（`sort -u \| wc -l` = 17） |
| `scroll-fade-size: 28px` | `.cds-root { --cds-scroll-fade-size: 28px; … }` |
