Ctrl K
ComunidadGuías…

DESIGN.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
---
version: alpha
name: DramaClaw
description: >-
  DramaClaw AIGC 视频引擎的视觉规范:暗色优先、信息密集的工作台。
  token 逐值对齐 frontend/src/index.css;以暗色为准,亮色值以 light-* 前缀并行。
colors:
  # ── 画布 / freezone 表面(来源:index.css 的 --*-rgb,.dark 块)──
  background: "#0a0c12"
  surface: "#15161d"
  surface-panel: "#13141b"
  surface-modal: "#1b1c23"
  surface-field: "#0d0e14"
  border-soft: "#1f2026"
  border-strong: "#2f3036"
  border: "#22232c"
  text: "#e8eaf0"
  text-muted: "#6f7079"
  accent: "#5ba0ff"
  # ── shadcn 语义层(来源:.dark 块的 oklch 值)──
  primary: "#00bdcf"
  primary-foreground: "#111b21"
  secondary: "#0e333c"
  foreground: "#e9edef"
  card: "#1f2c34"
  muted: "#182229"
  muted-foreground: "#8696a0"
  ui-border: "#2a3942"
  sidebar: "#111b21"
  destructive: "#ea4335"
  success: "#51bf6f"
  warning: "#efa831"
  chart-1: "#00bdcf"
  chart-2: "#009c9c"
  chart-3: "#31a7cd"
  chart-4: "#34b7f1"
  chart-5: "#51bf6f"
  # ── 亮色主题(来源::root 块)——角色相同,取值更亮 ──
  light-background: "#ffffff"
  light-surface: "#f5f5f5"
  light-border: "#e0e0e0"
  light-text: "#000000"
  light-text-muted: "#666666"
  light-accent: "#3b82f6"
  light-primary: "#008198"
  light-foreground: "#111b21"
  light-card: "#ffffff"
  light-muted: "#f0f2f5"
  light-muted-foreground: "#667781"
  light-ui-border: "#e9edef"
typography:
  display-lg:
    fontFamily: Inter
    fontSize: 30px
    fontWeight: 700
    lineHeight: 1.2
    letterSpacing: -0.02em
  headline-lg:
    fontFamily: Inter
    fontSize: 24px
    fontWeight: 600
    lineHeight: 1.33
  headline-md:
    fontFamily: Inter
    fontSize: 20px
    fontWeight: 600
    lineHeight: 1.4
  title-lg:
    fontFamily: Inter
    fontSize: 18px
    fontWeight: 600
    lineHeight: 1.55
  body-lg:
    fontFamily: Inter
    fontSize: 16px
    fontWeight: 400
    lineHeight: 1.5
  body-md:
    fontFamily: Inter
    fontSize: 14px
    fontWeight: 400
    lineHeight: 1.43
  body-sm:
    fontFamily: Inter
    fontSize: 12px
    fontWeight: 400
    lineHeight: 1.33
  label-md:
    fontFamily: Inter
    fontSize: 12px
    fontWeight: 500
    lineHeight: 1.25
  label-strong:
    fontFamily: Inter
    fontSize: 14px
    fontWeight: 600
    lineHeight: 1.43
  mono-sm:
    fontFamily: SFMono-Regular
    fontSize: 12px
    fontWeight: 400
    lineHeight: 1.33
  counter-pixel:
    fontFamily: PikoCountdownPixel
    fontSize: 72px
    fontWeight: 600
    lineHeight: 1
rounded:
  sm: 12px
  md: 14px
  lg: 16px
  xl: 20px
  field: 12px
  node: 14px
  panel: 16px
  full: 9999px
spacing:
  base: 4px
  xs: 4px
  sm: 8px
  md: 12px
  lg: 16px
  xl: 24px
  2xl: 32px
  button-y: 4px
  button-x: 11px
  node-gutter: 12px
components:
  modal:
    backgroundColor: "{colors.surface-modal}"
    textColor: "{colors.text}"
    rounded: "{rounded.xl}"
    padding: "{spacing.xl}"
  panel:
    backgroundColor: "{colors.surface-panel}"
    textColor: "{colors.text}"
    rounded: "{rounded.panel}"
    padding: "{spacing.md}"
  button-quiet:
    backgroundColor: "#0f1117"
    textColor: "{colors.text}"
    typography: "{typography.label-md}"
    rounded: "{rounded.full}"
    padding: 4px 11px
  button-quiet-hover:
    backgroundColor: "#15161c"
  button-quiet-primary:
    backgroundColor: "#0f1117"
    textColor: "{colors.accent}"
    typography: "{typography.label-md}"
    rounded: "{rounded.full}"
  button-quiet-primary-hover:
    backgroundColor: "#1f2a3f"
    textColor: "{colors.accent}"
  button-primary:
    backgroundColor: "{colors.primary}"
    textColor: "{colors.primary-foreground}"
    typography: "{typography.label-strong}"
    rounded: "{rounded.md}"
    height: 36px
    padding: 0 16px
  chip:
    backgroundColor: "#101118"
    textColor: "{colors.text-muted}"
    typography: "{typography.body-sm}"
    rounded: "{rounded.full}"
    padding: 2px 10px
  chip-active:
    backgroundColor: "#23334d"
    textColor: "{colors.accent}"
  field:
    backgroundColor: "{colors.surface-field}"
    textColor: "{colors.text}"
    typography: "{typography.body-md}"
    rounded: "{rounded.field}"
    padding: 8px 12px
    height: 36px
  field-focus:
    backgroundColor: "{colors.surface-field}"
    textColor: "{colors.text}"
  canvas-node:
    backgroundColor: "{colors.surface-panel}"
    textColor: "{colors.text}"
    rounded: "{rounded.node}"
    padding: "{spacing.md}"
  canvas-node-selected:
    backgroundColor: "{colors.surface-panel}"
    textColor: "{colors.accent}"
  popover:
    backgroundColor: "{colors.card}"
    textColor: "{colors.foreground}"
    rounded: "{rounded.lg}"
    padding: "{spacing.md}"
  badge-muted:
    backgroundColor: "{colors.muted}"
    textColor: "{colors.muted-foreground}"
    typography: "{typography.body-sm}"
    rounded: "{rounded.full}"
  status-failed:
    backgroundColor: "{colors.muted}"
    textColor: "{colors.destructive}"
    typography: "{typography.label-md}"
    rounded: "{rounded.full}"
  status-succeeded:
    backgroundColor: "{colors.muted}"
    textColor: "{colors.success}"
    typography: "{typography.label-md}"
    rounded: "{rounded.full}"
  status-degraded:
    backgroundColor: "{colors.muted}"
    textColor: "{colors.warning}"
    typography: "{typography.label-md}"
    rounded: "{rounded.full}"
  sidebar-item:
    backgroundColor: "{colors.sidebar}"
    textColor: "{colors.foreground}"
    typography: "{typography.body-md}"
    rounded: "{rounded.sm}"
    padding: "{spacing.sm}"
  # ── 亮色变体(无 `.dark` 时)。组件相同,改引 light-* token。──
  panel-light:
    backgroundColor: "{colors.light-card}"
    textColor: "{colors.light-foreground}"
    rounded: "{rounded.panel}"
    padding: "{spacing.md}"
  canvas-node-light:
    backgroundColor: "{colors.light-surface}"
    textColor: "{colors.light-text}"
    rounded: "{rounded.node}"
    padding: "{spacing.md}"
  field-light:
    backgroundColor: "{colors.light-background}"
    textColor: "{colors.light-text}"
    typography: "{typography.body-md}"
    rounded: "{rounded.field}"
    padding: 8px 12px
  chip-light:
    backgroundColor: "{colors.light-muted}"
    textColor: "{colors.light-text-muted}"
    typography: "{typography.body-sm}"
    rounded: "{rounded.full}"
  button-primary-light:
    backgroundColor: "{colors.light-primary}"
    textColor: "{colors.light-background}"
    typography: "{typography.label-strong}"
    rounded: "{rounded.md}"
    height: 36px
  button-quiet-primary-light:
    backgroundColor: "{colors.light-surface}"
    textColor: "{colors.light-accent}"
    typography: "{typography.label-md}"
    rounded: "{rounded.full}"
  badge-muted-light:
    backgroundColor: "{colors.light-muted}"
    textColor: "{colors.light-muted-foreground}"
    typography: "{typography.body-sm}"
    rounded: "{rounded.full}"
---

# DramaClaw DESIGN.md

本文件是 `frontend/src/index.css` 的机读镜像。CSS 变量一改,本文件必须同 commit 更新——
靠 `npx @google/design.md diff` 跟上一版比对,才能抓出视觉回归。

> 章节标题保持英文(Overview / Colors / …),因为 DESIGN.md 规范按英文标题解析章节,
> 改成中文会被 linter 判为「缺失章节」。token 名、CSS 变量名、类名、属性名、色值同理保持原文。

## Overview

DramaClaw(虾导)是一个专业 AIGC 视频工作台:节点画布、故事板、生成队列、长任务面板。
用户是会把工具开一整天的创作者,所以 UI 定性为**暗色优先、信息密集、克制**——
`theme: 'dark'` 是持久化的默认值,亮色是受支持的备选,不是主战场。

调性是「仪表盘,不是海报」。界面外壳退到近黑的半透明玻璃里;一屏之内唯一的高饱和色,
只属于用户正在操作的那个东西。密集是刻意的:12px 是主力字号,4–8px 是主力间距。
动效短促且减速——表面是「落定」,绝不回弹。

明确不要的东西:营销感渐变、装饰性插画、俏皮的回弹缓动、同一表面上出现第二种强调色。

以上以及本文件其余全部规则,适用范围是**登录之后的产品界面**。未登录就能访问的对外落地页
(下载页、登录页)是一小块有意划出的例外,边界写在 Scope & Exceptions 里。

## Colors

两套色板共存,但**同一个表面上不得混用**:

- **画布色板**(`background` #0a0c12 → `surface` #15161d → `border` #22232c,
  文字 `text` #e8eaf0 / `text-muted` #6f7079,强调色 `accent` #5ba0ff)。
  驱动 freezone 画布、节点主体、悬浮工具条,以及所有 `.tap-*` / `.ui-*` 类。
  取值以空格分隔的 RGB 三元组形式存放(`--bg-rgb`),便于叠加 alpha:
  `rgb(var(--accent-rgb) / 0.22)`。
- **shadcn 语义色板**(`primary` #00bdcf 电光青、`card` #1f2c34、
  `muted-foreground` #8696a0、`ui-border` #2a3942,外加 `destructive` / `success` /
  `warning`)。驱动 shadcn 原语——弹窗、下拉、Tab、表单——用 `oklch()` 书写以保证
  感知均匀。

规则:

- 语义状态色两套色板共用:`destructive` #ea4335 表示危险与失败,`success` #51bf6f
  表示完成,`warning` #efa831 表示降级或额度受限。**不要再造第二个红。**
- `accent`(蓝)和 `primary`(青)是**历史遗留的两个强调色**,不是设计决策。
  画布表面用蓝,shadcn 表面用青。按「表面归属哪套色板」来选,不要按品味选,
  另见 Do's and Don'ts。
- 静止表面上彩度必须低。shadcn 的 `secondary` / `accent` 是带青调的灰
  (暗色 #0e333c、亮色 #def6fa),正是为了让下拉和 Tab 的 hover 态足够安静。
- 分组默认填充使用 `--group-node-bg`:暗色 `rgba(255,255,255,0.1)`,亮色
  `rgba(15,23,42,0.09)`,让未设置配色的组也有可见边界。
- 亮色模式把 `background` 翻成纯白、`text` 翻成纯黑,强调色换 `light-accent` #3b82f6;
  `card` 变纯白,衬在 #f0f2f5 的页面底上。
- `chart-1` … `chart-5` 是唯一批准的数据序列色,走「青 → 蓝绿 → 天蓝 → 湖蓝 → 绿」,
  保证同一色系不重复出现。

## Typography

拉丁字形用 **Inter**,CJK 依次回退 `Noto Sans SC` → `PingFang SC` → `Microsoft YaHei`
(见 `--font-family-ui-default`;macOS 走更短的 `--font-family-ui-macos`)。
产品文案以中文为主,因此:

- **CJK 正文永远不要设 `letterSpacing`**。对 Inter 好看的字距会破坏汉字的字面间距。
  负字距只保留给 `display-lg`。
- 同样 px 下 CJK 字形看起来比拉丁小约一档。中英混排的标签优先用 `body-md`(14px),
  而不是 `body-sm`(12px)。
- `line-height` 一律用无单位数值,这样能随 CJK 回退字体更大的 x-height 一起缩放。

实际层级分工:`body-sm`(12px)和 `body-md`(14px)承担约 90% 的产品文本——
属性面板行、节点标签、任务列表。`label-md`(12px/500)用于 chip、按钮、工具条控件。
`headline-*` 每个面板标题只出现一次。`display-lg` 只留给空状态和弹窗标题。
`mono-sm` 仅用于 ID、路径、seed、时间码。`counter-pixel`(方舟像素数字子集)
只作用于 Piko 小游戏倒计时,不得外泄到产品界面。

## Layout & Spacing

**4px 基准单位**(`--spacing: 0.25rem`),之上叠 8/12/16 的节奏。这是密集型工具:
代码里出现最多的两个间距是 `gap-1`(4px)和 `gap-2`(8px),面板内边距是 8–12px,
不是 24px。

- 外壳:固定左侧栏 + 流式工作区。画布满幅铺开;面板浮在它上面,而不是把它挤变形。
- 悬浮面板(工具条、属性面板、popover)用 `position: fixed`、12px 内边距,
  并以 16px 安全边距被夹在视口内。
- 节点内部用 12px 的沟槽,元素间 8px 堆叠。属性面板行间距 4px;组之间用 12px 分隔,
  绝不用分割线。
- 列表和网格在 `.ui-scrollbar` 内滚动(7px 细滑块,`rgba(148,163,184,0.5)`)。
  横向滚动条通过 `.ui-scrollbar-vertical` 隐藏,但仍可用手势滚动。
- 页面 body 永不横向滚动。宽内容自带 `overflow-x: auto` 容器。

## Elevation & Depth

层次来自**近黑底上的半透明玻璃**,不是靠堆阴影。每个悬浮表面都是:
半透明填充 + 1px 细边 + 一层柔和阴影。

- **Level 0 — 底场:** `background` #0a0c12。不透明,永不模糊。
- **Level 1 — 节点 / 内联表面:** `surface-panel`,细边 `border-soft`
  (`rgba(255,255,255,0.05)`),不加模糊。
- **Level 2 — 悬浮面板**(`.tap-panel` / `.ui-panel`):
  `background: rgba(21,22,29,0.78)`、`border-soft`,以及
  `--ui-shadow-panel` = `0 14px 34px rgba(0,0,0,0.5)` 加一道
  `0 1px 0 rgba(255,255,255,0.03) inset` 顶部高光。压在画布上时配 `.backdrop-blur-tap`
  (`saturate(180%) blur(18px)`)。
- **Level 3 — 弹窗 / popover:** 产品工作台里的决策弹窗使用中性
  `surface-modal` #1b1c23 配 `ui-border`;通用 shadcn popover 继续使用 `card`。
  模态不模糊——它是要你做决定的。
- **项目卡(首页网格)** 是上面三层的一个刻意例外:填充与描边都用直给 alpha
  (`bg-white/[0.04]` → hover `[0.08]`、`border-white/10`),hover **不加阴影**——卡里的文件夹封面
  自带投影,再叠一层阴影会让反馈变成"变亮 + 浮起"两件事。代价是亮色主题下这两条白色叠加几乎
  不吃色,卡片轮廓只剩圆角;亮色要可见边就用 `dark:` 分档,不要改暗色值。
- **焦点与选中用发光,不用阴影:** hover-primary 用 `--ui-glow-accent` =
  `0 0 24px rgb(var(--accent-rgb) / 0.25)`;聚焦输入框用
  `0 0 0 3px rgb(var(--accent-rgb) / 0.12)` 的 ring。
- 亮色模式把同样三层换成 #f0f2f5 上的不透明白卡片,阴影柔和得多
  (`0 2px 10px rgba(0,0,0,0.10)`);玻璃隐喻在那边是刻意弱化的。

动效 token 也是层次的一部分:`--duration-fast` 150ms 用于 hover 和变色,
`--duration-base` 220ms 用于面板入场,`--duration-slow` 320ms 用于布局位移,
一律搭配 `--ease-out-quint` `cubic-bezier(0.22,1,0.36,1)` 或 `--ease-standard`。
**绝不用 bounce / elastic**——真实物体只会平滑减速。

## Shapes

两套圆角家族,相对小字号都偏大:

- **胶囊**(`rounded.full`,9999px)用于一切「可点但不是提交」的东西:安静按钮、
  chip、筛选器、标签。这是签名形状——`rounded-full` 是代码里用得最多的圆角。
- **柔和矩形**用于容器:输入框 `field` 12px,画布节点 `node` 14px,
  悬浮面板 `panel` 16px,大抽屉 `xl` 20px。

`--radius` 是 1rem,shadcn 的档位由它推导(`sm` 12px → `xl` 20px),所以这个项目里
Tailwind 的 `rounded-md` 是 14px,不是 6px。不要写死圆角,引用这些 token。
图标 1.5–2px 描边、圆头端点,尺寸取 14/16/20px,与 `label-md` 保持同一视觉基线。

## Components

**安静按钮**(`.tap-button`)是默认的操作控件:胶囊、4px/11px 内边距、12px 标签、
细边框、半透明填充。它在 hover 之前不宣示任何存在感;hover 时边框加强、填充升到
`surface`/0.88。它的主操作变体(`.tap-button-quiet-primary`)只把**文字**染成 `accent`
并加上强调色发光——它仍然是一个安静按钮。禁用态是 `opacity: 0.4` 加
`cursor: not-allowed`;不要再单独把标签置灰。

**实心按钮**(shadcn `button-primary`,近黑底上的青)每屏最多一个:表单或弹窗的提交动作。
画布上优先用安静主操作按钮。

**Chip**(`.tap-chip`)是带弱化文字的胶囊;`data-state="active"` 时文字翻成 `accent`,
底色变 `accent`/0.22 填充并加 1px 强调色 ring。激活状态由 `data-state` 承载,不用类名开关,
这样 React Flow 的 portal 也能被样式命中。

**输入框**(`.tap-field` / `.ui-field`)是 `surface-field` 上 12px 圆角的输入,静止态无可见描边;
聚焦时加 0.5 alpha 的强调色边框和 3px 强调色 ring。占位符和辅助文字有专属 token
(`--canvas-node-input-placeholder`、`--canvas-node-input-helper`),
让节点输入压在玻璃上仍然可读——用它们,不要用 `text-muted`。

**画布节点**是 Level 1 表面,14px 圆角、12px 沟槽。选中态是强调色 ring 加强调色标题;
绝不用投影——那读起来像正在拖拽。

**Popover / 下拉 / tooltip** 用不透明的 `card` 表面配 `ui-border`、16px 圆角,
hover 行用 shadcn 的 `accent`(#0e333c)——刻意不用画布的蓝强调色,
因为它们渲染在语义色板里。

## Do's and Don'ts

- **要**从 `frontend/src/index.css` 的 CSS 变量里读颜色、圆角、间距、动效。
  **不要**在组件里写死 hex、px 圆角或 ms 时长——写死字面量正是两套色板开始漂移的方式。
- **要**按「表面归属哪套色板」决定用蓝还是青(画布 → `accent` 蓝,shadcn 原语 →
  `primary` 青)。**不要**引入第三个强调色,也**不要**只改一侧表面去「对齐」另一侧。
  统一这两个色相是已知的待决事项,必须作为一次跨两套色板的有意改动落地。
- **要**保证一屏只有一个高饱和元素。**不要**把实心主按钮、激活 chip、发光节点堆在一起——
  眼睛会找不到主体。
- **要**用 `rgb(var(--x-rgb) / a)` 合成半透明,这样一条声明同时适配两种主题。
  **不要**在组件里手写 `rgba(255,255,255,0.05)`;用 `--ui-border-soft` /
  `--ui-border-strong`。
- **要**在压住画布的半透明面板上配 `.backdrop-blur-tap`。**不要**给全屏模态加模糊,
  也不要嵌两层模糊——第二层模糊要付一次全视口合成的代价,却看不出差别。
- **要**让任何用户必须读的文字满足 WCAG AA(4.5:1)。有四处**已知欠账**已经实测记录,
  它们是债务而不是先例——不要把这些比值抄进新组件:
  - `chip` 静止态标签,`text-muted` 压在 chip 填充上——**3.83:1**
    (「未激活就安静」的意图,但对必读标签而言低于 AA)
  - `status-failed`,`destructive` #ea4335 压在 `muted` 上——**4.12:1**
    (暗色下的错误文字;修法是文字改用更亮的红,填充保留 #ea4335)
  - `button-quiet-primary-light`,`light-accent` #3b82f6 压在 `light-surface` 上——
    **3.37:1**(亮色模式最薄弱的一环)
  - `badge-muted-light`,`light-muted-foreground` 压在 `light-muted` 上——**4.14:1**
- **要**放心用 `body-sm`(12px)承载元信息,中英混排一律用 `body-md`(14px)。
  **不要**低于 12px,也不要在同一个面板里用超过两种字重。
- **要**把 hover 反馈控制在 150ms 内、面板入场 220ms 并配 `ease-out-quint`。
  **不要**在画布表面上动画 `width`/`height`/`top`/`left`——只动 transform 和 opacity。
- **要**在收工前验两套主题:给 `<html>` 切 `.dark`,重点看玻璃表面——它们是亮色下
  最先崩的东西。

## Scope & Exceptions

本文件默认约束的是**登录之后的产品界面**——画布、面板、列表、弹窗,一切用户开一整天的东西。
未登录就能访问的**对外落地页**是唯一的例外区,因为它们的任务不同:产品界面要让人忘记界面本身,
落地页要在十秒内说服一个还没有账号的人。用工作台的 token 去做落地页,结果是一张没人会转发的
说明书。

例外仅限以下两处,**是白名单,不是「凡是新页面都能声明例外」**:

- `/download` — `src/components/download/download.module.css`、`DownloadCanvasPreview.tsx`
- `/login` — `src/components/login/login.module.css`、`poster-wall.module.css`、`light-rays.css`

新增落地页要进这份白名单,必须同 commit 更新本节。任何登录后可达的界面一律不在其中,
包括从落地页跳进去的第一屏。

### 例外区内允许的偏离

- **自带色板,不引 `index.css` 的 token。** 下载页取自品牌 DC 标的材质——镀锌钢
  (`--zinc` #a8b4be)加锈蚀氧化(`--rust` #d9603a / `--rust-hot` #ee7549)。
  这确实是第三个强调色,只在例外区内成立:**`rust` 一族不得出现在任何产品界面里**,
  且在页内继续守「一屏一个主导高饱和元素」——静止态的锈橙只给 hero 的 `DC` 字样、
  「你的系统」标记、主下载按钮、hero 底部校准条,以及预览 SVG 里正在渲染的那个节点;
  其余出现的地方只能是焦点环和 hover 反馈。
- **拉丁字形用 Geist Variable**(`@fontsource-variable/geist`),CJK 回退链不变。
  字体只能随落地页的 lazy chunk 加载;一旦它出现在主 chunk 里,例外就失效了。
- **元信息层可以低到 10px,包括中文标签。** 落地页是海报式排版,行宽和留白都远大于工作台,
  等宽刻度字压到 10–11.5px 才有仪器感:眉标(`.eyebrow` 11px)、规格表键名(`.spec dl` 11.5px)、
  校验和值与说明(10.5 / 11px)、更新日志时间(11.5px)。边界:**这一档只给「扫一眼就够、
  不需要逐字读」的标签**——凡是需要读完的段落,字号仍守住产品的 12px 下限
  (落地页实测 12.5–17px:`.heroNote` 12.5px、`.capabilityCell p` / `.changelog p` 13.5px、
  `.faqAnswer` 14px、hero 正文 16–17px)。中文在 11px 是有代价的,别把这一档扩大到正文。
- **进场与装饰动效可以超过 320ms。** 区块进场 700ms、hero 校准条推进 1400ms,都配
  `cubic-bezier(0.16,1,0.3,1)` 减速,且只允许动 `opacity` / `transform` / `width`。
  交互反馈也放宽了一档:hover / 焦点 / 展开走 140–240ms,而不是产品界面的 150ms 上限——
  落地页的元素更大、位移更长,150ms 会显得生硬。边界:**交互反馈的上限仍是产品的 320ms
  `--duration-slow`**,超过 320ms 的只能是一次性进场和装饰动画;依然禁 bounce / elastic。
- **允许营销渐变与装饰图形。** hero 与卡片底纹用 radial / linear 渐变,画布预览是一张内联 SVG。

### 例外区内依然不许违反的

- 暗色优先。落地页锁死深色,不提供亮色变体。
- 用户必须读的文字满足 WCAG AA(4.5:1)。
- body 永不横向滚动;宽内容自带 `overflow-x: auto`。
- `@media (prefers-reduced-motion: reduce)` 必须把动画关掉——超长动效正是这条不能省的原因。
- 例外区的样式必须收在自己的样式文件里(优先 CSS Module;`light-rays.css` 这类页面私有全局
  CSS 要保证类名带页面前缀)。**不得反向修改 `index.css` 的全局变量**,也不得把落地页的类名
  泄漏到产品界面。

`design.md lint` 只校验本文件自身,不读 CSS,所以这一节是靠 review 执行的约定,不是靠工具。
评审落地页时看这一节;评审其余界面时,这一节不适用。

## Linting

```bash
npx @google/design.md lint DESIGN.md          # 0 errors 是准入线
npx @google/design.md diff old.md DESIGN.md   # token 级视觉回归
npx @google/design.md export DESIGN.md --format css-vars   # 或 tailwind | dtcg
```

预期基线:**0 errors,15 warnings。** 其中 4 条是上面列出的实测对比度欠账;
11 条是 `orphaned-tokens`,落在 `border` / `border-soft` / `border-strong` /
`ui-border` / `light-border` / `light-ui-border` / `chart-1..5` 上——
alpha 版规范既没有 border 属性也没有图表序列概念,这些 token 无法被 component 引用。
**不要为了消警告删掉它们**,它们在 `index.css` 里是承重的。
出现任何**新的 error**,或 warning 数超过 15,都意味着回归。