@@ -76,6 +76,37 @@ export const SHADOW_TOKENS = [
7676 { name : "shadow-2" , light : "0 2px 4px rgba(28,25,23,.06), 0 12px 32px rgba(28,25,23,.14)" , dark : "0 0 0 1px rgba(255,255,255,.06)" , usage : "高层:Modal / 浮窗(亮档投影;暗档反相描边)" } ,
7777] ;
7878
79+ /**
80+ * 动效时长 token(规格 §8.4)—— 批 6 从 `src/ui/primitives/motion.css` 的临时接缝**逐字迁入**(R2.3)。
81+ *
82+ * Why 进真源:`motion.css` 的那段 `:root{}` 是批 0-D 的**临时**落点(当时还没有动效真源),文件头逐字
83+ * 承诺「批 6 的动效 token 真源完成后整块删除」。迁移后本文件是时长/缓动的**唯一真源**(R1.1 单一真源,
84+ * 绝不双写):`motion.css` 里不再允许出现 `--ed-*` 定义,由 `style-seams.test.ts` 的回归封条守。
85+ * 值**逐字照抄**临时块、一个不改;消费点写的是 `var(--ed-dur-x, <同值字面量>)` ⇒ 删块即生效、零规则改动。
86+ *
87+ * 边界:`ms` 是**纯数字**(`renderCss` 负责拼 `ms` 单位);`usage` 只进 TS 产物,不进 CSS。
88+ */
89+ export const DURATION_TOKENS = [
90+ { name : "micro" , ms : 120 , usage : "响应层:单属性、无时序的交互回执(规格 §8.2 判据)" } ,
91+ { name : "overlay-in" , ms : 200 , usage : "弹层遮罩与面板进场(规格 §8.4「进出场:弹层 200/160」)" } ,
92+ { name : "overlay-out" , ms : 160 , usage : "弹层遮罩与面板出场 —— 出场比进场快(规格 §8.4)" } ,
93+ { name : "toast-in" , ms : 180 , usage : "Toast 进场(规格 §8.4「Toast 180/140」)" } ,
94+ { name : "toast-out" , ms : 140 , usage : "Toast 出场 —— 出场比进场快(规格 §8.4)" } ,
95+ { name : "skeleton" , ms : 1200 , usage : "循环环境动效:骨架微光(规格 §8.4「--dur-skeleton 1200ms」)" } ,
96+ { name : "card" , ms : 220 , usage : "面板 / 视图 / 列折叠(规格 §8.4「--dur-card 220ms」);今日 0 生产消费者,波 C 首次消费" } ,
97+ { name : "reveal" , ms : 500 , usage : "显影 / 编排层(规格 §8.4「--dur-reveal 500ms」);今日 0 生产消费者,波 C 首次消费" } ,
98+ { name : "page" , ms : 150 , usage : "页面切换(规格 §8.4「--dur-page 150ms」);今日 0 生产消费者,波 C 首次消费" } ,
99+ ] ;
100+
101+ /**
102+ * 缓动 token(规格 §8.4「`--ease cubic-bezier(0.2,0,0,1)`」)—— 与时长**分开列**:
103+ * 变量名是 `--ed-ease`(没有 `dur-` 段),而时长恒为 `--ed-dur-<name>`,两者的拼法不同名。
104+ * 批 6 的 T8 会在此追加双基调的两条缓动(`data-tone` 的仪器 / 纸),**只许追加、不许改本条**。
105+ */
106+ export const EASING_TOKENS = [
107+ { name : "ease" , value : "cubic-bezier(0.2, 0, 0, 1)" , usage : "全站唯一曲线(规格 §8.4)" } ,
108+ ] ;
109+
79110/** 非颜色 token:两档共用(规范 §4.2) */
80111export const SCALE_SOURCE = {
81112 fontFamilyBody : '"Source Han Serif SC", "Songti SC", SimSun, serif' ,
@@ -161,6 +192,10 @@ ${SHADOW_TOKENS.map((t) => ` --ed-${t.name}: ${t.light}; /* ${t.usage} */`).joi
161192 /* 壳层纵向基准(规格 §1 决策 16):顶栏高度 —— 页面用 calc(100vh - var(--ed-nav-h)) 消费 */
162193 --ed-nav-h: ${ SCALE_SOURCE . navHeight } px;
163194
195+ /* 动效(规格 §8.4):9 个时长 + 1 个缓动 —— 批 6 从 primitives/motion.css 的临时接缝迁入,值逐字不变 */
196+ ${ DURATION_TOKENS . map ( ( t ) => ` --ed-dur-${ t . name } : ${ t . ms } ms;` ) . join ( "\n" ) }
197+ ${ EASING_TOKENS . map ( ( t ) => ` --ed-${ t . name } : ${ t . value } ;` ) . join ( "\n" ) }
198+
164199 /* 图标 */
165200 --ed-icon-stroke: ${ SCALE_SOURCE . iconStroke } ;
166201}
@@ -178,6 +213,21 @@ function renderTs() {
178213 const colorRows = COLOR_TOKENS . map (
179214 ( t ) => ` { name: ${ JSON . stringify ( t . name ) } , light: ${ JSON . stringify ( t . light ) } , dark: ${ JSON . stringify ( t . dark ) } , usage: ${ JSON . stringify ( t . usage ) } },` ,
180215 ) . join ( "\n" ) ;
216+ const durationRows = DURATION_TOKENS . map (
217+ ( t ) => ` { name: ${ JSON . stringify ( t . name ) } , ms: ${ t . ms } , usage: ${ JSON . stringify ( t . usage ) } },` ,
218+ ) . join ( "\n" ) ;
219+ const easingRows = EASING_TOKENS . map (
220+ ( t ) => ` { name: ${ JSON . stringify ( t . name ) } , value: ${ JSON . stringify ( t . value ) } , usage: ${ JSON . stringify ( t . usage ) } },` ,
221+ ) . join ( "\n" ) ;
222+ // 合并名册:与上面两份同源派生(同一份 DURATION_TOKENS/EASING_TOKENS 数据),不是第二份手写数据
223+ const motionRows = [
224+ ...DURATION_TOKENS . map (
225+ ( t ) => ` { name: ${ JSON . stringify ( `dur-${ t . name } ` ) } , cssVar: ${ JSON . stringify ( `--ed-dur-${ t . name } ` ) } , kind: "duration", value: ${ JSON . stringify ( `${ t . ms } ms` ) } },` ,
226+ ) ,
227+ ...EASING_TOKENS . map (
228+ ( t ) => ` { name: ${ JSON . stringify ( t . name ) } , cssVar: ${ JSON . stringify ( `--ed-${ t . name } ` ) } , kind: "easing", value: ${ JSON . stringify ( t . value ) } },` ,
229+ ) ,
230+ ] . join ( "\n" ) ;
181231 return `${ TS_HEADER } export interface ColorToken {
182232 /** 不含 \`--ed-\` 前缀;完整变量名用 \`cssVar(name)\` 组装 */
183233 readonly name: string;
@@ -214,6 +264,46 @@ export const SCALE_TOKENS = {
214264 iconSizes: ${ JSON . stringify ( SCALE_SOURCE . iconSizes ) } ,
215265} as const;
216266
267+ export interface DurationToken {
268+ /** 不含 \`--ed-dur-\` 前缀;完整变量名 = \`--ed-dur-\` + name */
269+ readonly name: string;
270+ readonly ms: number;
271+ readonly usage: string;
272+ }
273+
274+ export interface EasingToken {
275+ /** 不含 \`--ed-\` 前缀(今日只有全站唯一曲线 \`ease\`) */
276+ readonly name: string;
277+ readonly value: string;
278+ readonly usage: string;
279+ }
280+
281+ /** 动效时长(规格 §8.4)—— 值**逐字**来自批 0-D 的临时接缝 primitives/motion.css,批 6 迁入此处 */
282+ export const DURATION_TOKENS = [
283+ ${ durationRows }
284+ ] as const satisfies readonly DurationToken[];
285+
286+ /** 时长短名的字面量联合:门面收窄入参用(拼错必须编译期报错,不是运行期静默取不到值) */
287+ export type DurationTokenName = (typeof DURATION_TOKENS)[number]["name"];
288+
289+ /** 缓动曲线(规格 §8.4)—— 批 6 的 T8 会追加双基调的两条,只许追加 */
290+ export const EASING_TOKENS = [
291+ ${ easingRows }
292+ ] as const satisfies readonly EasingToken[];
293+
294+ /**
295+ * 时长 + 缓动的**合并名册**:\`name\` 是不含前缀的短名(\`dur-micro\` / \`ease\`),
296+ * \`cssVar\` 是 CSS 变量全名,\`value\` 是 CSS 里的字面量(时长带 \`ms\` 单位)。
297+ *
298+ * Why:消费方(守卫 / GSAP / 文档回写)要的正是「变量全名 ↔ 定值」这一层;两份数据同源派生,
299+ * 故合并名册不可能与上面两份分叉。
300+ */
301+ export const MOTION_TOKENS = [
302+ ${ motionRows }
303+ ] as const;
304+
305+ export type MotionTokenName = (typeof MOTION_TOKENS)[number]["name"];
306+
217307export const THEMES = ["light", "dark"] as const;
218308export type ThemeName = (typeof THEMES)[number];
219309` ;
0 commit comments