初始提交:三玩法 Web 原型与设计文档
- 玩法一 数字一笔:规则库、求解器、26 关、竖屏触摸 Web 原型、测试 - 玩法二 连连气球:连消畅玩版核心、Web 原型、Cocos 球槽组件与导出脚本 - 玩法三 六边一笔:规则库、求解器、10 关 + 转墙实验包、Web 原型、测试 - 产品文档、UI 设计规范、各玩法 GDD / HANDOFF - 文档移除全部外部参考作引用;删除玩法二 v0 旧稿
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
// 设计令牌单一来源守门:web/style.css 的 :root 与 src/theme.ts、web/canvas-painter.ts 必须一致。
|
||||
// 改任一处而不同步另一处,这里会红。规格文档见仓库 docs/design.md。
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
import { test } from 'node:test';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { MOTION, THEME } from '../src/theme';
|
||||
import { BOARD_FONT_FAMILY } from '../web/canvas-painter';
|
||||
|
||||
const here = dirname(fileURLToPath(import.meta.url));
|
||||
const css = readFileSync(resolve(here, '../web/style.css'), 'utf8');
|
||||
|
||||
function rootTokens(source: string): Map<string, string> {
|
||||
const m = /:root\s*\{([\s\S]*?)\n\}/.exec(source);
|
||||
assert.ok(m, 'style.css 缺少 :root 块');
|
||||
const out = new Map<string, string>();
|
||||
for (const line of m[1].split('\n')) {
|
||||
const t = /^\s*(--[\w-]+)\s*:\s*(.+?);\s*$/.exec(line);
|
||||
if (t) out.set(t[1], t[2]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
const tokens = rootTokens(css);
|
||||
const hex = (s: string | undefined): string => (s ?? '').trim().toUpperCase();
|
||||
|
||||
test('颜色令牌:style.css :root 与 THEME 一致', () => {
|
||||
const pairs: Array<[string, string]> = [
|
||||
['--bg', THEME.bg],
|
||||
['--cell', THEME.cell],
|
||||
['--ink', THEME.clueInk],
|
||||
['--clue-ring', THEME.clueRing],
|
||||
['--primary', THEME.visited],
|
||||
['--primary-bright', THEME.visitedBright],
|
||||
['--on-primary', THEME.visitedInk],
|
||||
['--danger', THEME.conflict],
|
||||
['--hint', THEME.hint],
|
||||
];
|
||||
for (const [name, value] of pairs) {
|
||||
assert.equal(hex(tokens.get(name)), hex(value), `${name} 与 theme.ts 不一致`);
|
||||
}
|
||||
});
|
||||
|
||||
test('动效时长令牌:--t-* 与 MOTION 一致', () => {
|
||||
const pairs: Array<[string, number]> = [
|
||||
['--t-fast', MOTION.fast],
|
||||
['--t-base', MOTION.base],
|
||||
['--t-slow', MOTION.slow],
|
||||
['--t-hold', MOTION.hold],
|
||||
];
|
||||
for (const [name, seconds] of pairs) {
|
||||
assert.equal(tokens.get(name), `${Math.round(seconds * 1000)}ms`, `${name} 与 theme.ts MOTION 不一致`);
|
||||
}
|
||||
assert.equal(MOTION.numberFade, MOTION.fast, '步号淡入应等于 fast 档');
|
||||
assert.equal(MOTION.bridgeGrow, MOTION.fast, '桥生长应等于 fast 档');
|
||||
assert.equal(MOTION.flashTime, MOTION.slow, '闪白应等于 slow 档');
|
||||
assert.equal(MOTION.clearedSweep, MOTION.slow, '过关扫光应等于 slow 档');
|
||||
assert.equal(MOTION.clearedHold, MOTION.hold, '过关停留应等于 hold 档');
|
||||
assert.equal(MOTION.boardEnter, MOTION.base, '进场应等于 base 档');
|
||||
});
|
||||
|
||||
test('字体栈:canvas 与 CSS --font 完全一致', () => {
|
||||
assert.equal(tokens.get('--font'), BOARD_FONT_FAMILY);
|
||||
});
|
||||
Reference in New Issue
Block a user