feat: add Paper Routes and package five design systems

Add the paper direction with thin tracks, outlined hexagons and line arrows, without exterior color markers or piece numbers. Add a unified catalog for five designs and their 25 themes, with direct review links.

Organize each design into an authored design.md, generated tokens.json and preview. Add Agent routing, shared integration guidance, a machine-readable manifest and an executable rendering example. Preserve legacy preview and document links.

Validation: 16 focused design, theme and package tests passed; npm run design and static build passed. Confirmed all eight authored documents survive generation unchanged. Browser checks covered mobile, tablet and desktop layouts, theme links and migrated previews. Native engine integration and visual verification remain pending.
This commit is contained in:
cabinw
2026-09-13 13:24:34 +08:00
parent e32a1616be
commit 10cf93a324
52 changed files with 3365 additions and 149 deletions
+4
View File
@@ -36,3 +36,7 @@ Before calling MCP tools, read `/Users/wukaibing/.grok-go/agents-guide.md` for v
### Level-production scope ### Level-production scope
The user has explicitly limited this work to level creation: board layouts, track connections, pieces, difficulty, and level ordering. Use the game's existing interface and interaction logic. Do not independently add controls, hide controls, change layouts or themes, or introduce teaching-specific interface behavior. Any future interface or interaction change requires an explicit user request. Preserve existing campaign numbering and saved progress when integrating level data. The user has explicitly limited this work to level creation: board layouts, track connections, pieces, difficulty, and level ordering. Use the game's existing interface and interaction logic. Do not independently add controls, hide controls, change layouts or themes, or introduce teaching-specific interface behavior. Any future interface or interaction change requires an explicit user request. Preserve existing campaign numbering and saved progress when integrating level data.
### Design-system entry point
For user-requested design/UI work, start at [design/README.md](design/README.md) and [design/AGENTS.md](design/AGENTS.md), then read [design/shared.md](design/shared.md) and the selected `design/directions/<id>/design.md`. [design/manifest.json](design/manifest.json) lists all five stable IDs, canonical documents, previews, and generated tokens. Each design has five themes; design IDs are separate from theme IDs and campaign versions. Authored design.md files must not be overwritten by generators. This entry point does not expand the scope of level-only tasks.
+13 -7
View File
File diff suppressed because one or more lines are too long
+28 -10
View File
@@ -2,7 +2,9 @@
版本:2.0 · 2026-09-08 · 适用:网页、手机触摸界面与 Cocos 移植。 版本:2.0 · 2026-09-08 · 适用:网页、手机触摸界面与 Cocos 移植。
本文件规定设计角色和使用方式;`src/design-tokens.js` 是颜色数值的唯一来源。修改数值后运行 `npm run design`,同步生成 CSS、JSON、面板 PNG、对比度报告和可视化手册 `Design.html`。HTML 为单文件离线版本,包含原始图片、色值复制、组件示例、弹窗动效与本文全文。新界面不得另写一套 HEX 或用滤镜临时拼配色。 本文件保留基础方案与通用组件的设计规范。五套方案的 Agent 入口为 [design/README.md](design/README.md),各套独立规则见 `design/directions/<id>/design.md`;本文的基础哑光造型和配色要求不覆盖其他四套的独立画法。
`src/design-tokens.js` 是基础语义色来源,`src/art-directions.js` 定义各套独立配色。修改数值后运行 `npm run design`,同步生成 CSS、JSON、面板 PNG、对比度报告、设计包和可视化手册 `Design.html`。HTML 为单文件离线版本,包含原始图片、色值复制、组件示例、弹窗动效与本文全文。新界面应复用所选方案的颜色源,不另写一份重复色库。
## 1. 设计方向 ## 1. 设计方向
@@ -107,11 +109,11 @@
旋转操作为放大的生成箭头,下方分别写“左转”“右转”,不加矩形底板。文字用主文字色;白色箭头允许轻微蓝灰轮廓阴影增强边缘识别,不能改写原图轮廓。它们须保留至少 44×44 的触摸区域。 旋转操作为放大的生成箭头,下方分别写“左转”“右转”,不加矩形底板。文字用主文字色;白色箭头允许轻微蓝灰轮廓阴影增强边缘识别,不能改写原图轮廓。它们须保留至少 44×44 的触摸区域。
撤销、重来、提示使用独立彩色底座:原版为蓝、紫、金;高彩为青、荧粉、亮黄;玩具为蓝、红、黄圆角积木键;动物为绿、棕、麦黄徽章。四套方案的底部道具均采用纯色平面,不使用投影、内阴影、立体底边或渐变高光;图标和未来次数/广告角标同样不加阴影。普通底座 64px,大屏 68px,短屏 56px;图标 32px、标签 13px。整体点击区至少 80×96px,短屏 80×84px。禁用时确实停止操作,道具透明度为 50%;不得只有颜色变化却仍可点击。 撤销、重来、提示使用独立彩色底座:原版为蓝、紫、金;高彩为青、荧粉、亮黄;玩具为蓝、红、黄圆角积木键;动物为绿、棕、麦黄徽章;轻线纸境为雾蓝、陶橙、赭金平面底座。五套方案的底部道具均采用纯色平面,不使用投影、内阴影、立体底边或渐变高光;图标和未来次数/广告角标同样不加阴影。普通底座 64px,大屏 68px,短屏 56px;图标 32px、标签 13px。整体点击区至少 80×96px,短屏 80×84px。禁用时确实停止操作,道具透明度为 50%;不得只有颜色变化却仍可点击。
右上角预留 27px 次数角标或 42px 广告角标,距底座上缘 -5px;广告使用播放符号 + AD,不能只有模糊的加号。两种角标互斥,不遮挡图标与标签。设计稿仅展示“撤销 3 次”和“广告获取提示”的外观,不接入库存、扣次数、广告播放或奖励。实际次数、定价与获取方式待产品确定。 右上角预留 27px 次数角标或 42px 广告角标,距底座上缘 -5px;广告使用播放符号 + AD,不能只有模糊的加号。两种角标互斥,不遮挡图标与标签。设计稿仅展示“撤销 3 次”和“广告获取提示”的外观,不接入库存、扣次数、广告播放或奖励。实际次数、定价与获取方式待产品确定。
统一审核页的“道具设计”区域、四套独立设计稿与离线手册均包含普通态和未来角标示例,允许在同一页面切换比较。 统一审核页的“道具设计”区域、五套独立设计稿与离线手册均包含普通态和未来角标示例,允许在同一页面切换比较。
### 棋盘和轨道 ### 棋盘和轨道
@@ -194,13 +196,29 @@ Cocos 读取同一 JSON/JS token 和 PNG。`CandyUiSkin` 使用纯色 Sprite、
## 多设计方案审核 ## 多设计方案审核
新增四套并列设计(现有方案 + 三套实验方案),每套均包含轻松、进阶、烧脑、挑战、每周挑战五种主题。设计切换同时作用于棋子造型、轨道材质与主题配色;共享同一弯曲关卡与动画逻辑。 Agent 接入先读 [design/README.md](design/README.md),共用规则见 [design/shared.md](design/shared.md)。每套独立目录包含人工维护的 design.md、生成配色 tokens.json 和预览 index.html;五套方案不能只用本手册的基础色值描述。
| 设计 | 视觉特征 | 独立设计稿 | 共有五套并列设计(现有方案 + 三套实验方案 + 玩法实验页衍生方案),每套均包含轻松、进阶、烧脑、挑战、每周挑战五种主题。设计切换同时作用于棋子造型、轨道材质与主题配色;共享同一弯曲关卡与动画逻辑。
| --- | --- | --- |
| 柔和原色 | 哑光色块、蓝灰轨道 | [柔和原色](design/directions/calm.html) | | 编号 | 设计 | 视觉特征 | 独立设计稿 |
| 高彩游乐场 | 高饱和撞色、粗轮廓、贴纸硬阴影 | [高彩游乐场](design/directions/vivid.html) | | --- | --- | --- | --- |
| 积木轨道屋 | 塑胶高光、分层厚度、圆角积木 | [积木轨道屋](design/directions/toy.html) | | 01 | 柔和原色 | 哑光色块、蓝灰轨道 | [柔和原色](design/directions/calm/index.html) |
| 动物弯弯镇 | 猫、熊、兔、蛙造型与独立箭头 | [动物弯弯镇](design/directions/animals.html) | | 02 | 高彩游乐场 | 高饱和撞色、粗轮廓、贴纸硬阴影 | [高彩游乐场](design/directions/vivid/index.html) |
| 03 | 积木轨道屋 | 塑胶高光、分层厚度、圆角积木 | [积木轨道屋](design/directions/toy/index.html) |
| 04 | 动物弯弯镇 | 猫、熊、兔、蛙造型与独立箭头 | [动物弯弯镇](design/directions/animals/index.html) |
| 05 | 轻线纸境 | 浅色细轨道、微小格缝、细白边六角与线条箭头 | [轻线纸境](design/directions/paper/index.html) |
在 [设计总览](design/index.html) 并排比较各套画法、切换统一主题,完整编号与 25 种主题见 [方案目录](design/README.md)。最新是第 05 套「轻线纸境」,五套方案均可并列保留。
在 [统一审核页](design-showcase.html) 切换设计及主题,可同时保留多套、逐套记录意见并导出 JSON。意见保存在当前浏览器。设计稿由 `scripts/build-art-directions.mjs` 生成,颜色及造型规则来自 `src/art-directions.js`、`src/token-art.js`。 在 [统一审核页](design-showcase.html) 切换设计及主题,可同时保留多套、逐套记录意见并导出 JSON。意见保存在当前浏览器。设计稿由 `scripts/build-art-directions.mjs` 生成,颜色及造型规则来自 `src/art-directions.js`、`src/token-art.js`。
### 轻线纸境 · 实验页画法提取
来源为 [玩法变种实验页](labs/variants/index.html) 的棋盘绘制,不改动实验页的关卡或玩法。独立设计稿中以「双色试航」的初始布局作对照,保留陶橙/雾蓝棋子、细白边与线条箭头;统一审核页使用与其他方案相同的弯曲轨道测试关,供多选比较。
- 轻松主题沿用背景 `#EAF0F3`、格面 `#FAFBF7`、分格 `#D3DFE3`、轨道 `#B2C4CE`,核心棋子为陶橙 `#D66C4D` 与雾蓝 `#477FC0`。
- 格面半径为格半径的 0.97,棋子为 0.88;格线宽度 0.025、轨道线宽 0.055、棋子白边 0.035、箭头线宽 0.09,均相对于格半径。极小缩略图的轨道最小 0.7px、格线最小 0.4px。
- 棋子为正六角纯色面,细白边;箭头由一根直线和开放的箭头头部组成,圆头圆接缝。移动时同样使用此画法,不切回原版粗实心箭头。
- 五种主题为雾蓝信纸、暖砂手札、鼠尾草页、石墨练习、夜航图纸;保留 12 色库与每关 4/7/9/12 色对照,所有白色棋子箭头对比度至少 3:1。
- 浅色细轨道忠实保留用户认可的参考画面,轨道本身低于原版 3:1 目标;这是一份待视觉审核的设计方案,不宣称通过轨道可访问性对比度标准。页面文字与棋子方向仍分别检查可读性。
- 延续当前居中棋盘、大尺寸扁平道具、通知位置与动画时序;线条和六角形可通过现有跨引擎绘制命令实现。原生引擎尚未实机验收。
+3
View File
@@ -29,6 +29,7 @@ npm run dev
- [千关审核页](http://localhost:4173/labs/campaign-review/index.html):按章看布局,检查制作证据,逐步回放和导出本章总览。 - [千关审核页](http://localhost:4173/labs/campaign-review/index.html):按章看布局,检查制作证据,逐步回放和导出本章总览。
- [花形轨道测试](http://localhost:4173/labs/flower/index.html):双层十二瓣与三重环带编织成花,包含 10 枚棋子的独立可玩实验,详见 [设计说明](docs/flower-study.md)。 - [花形轨道测试](http://localhost:4173/labs/flower/index.html):双层十二瓣与三重环带编织成花,包含 10 枚棋子的独立可玩实验,详见 [设计说明](docs/flower-study.md)。
- [交互设计规范](http://localhost:4173/Design.html):色板、图标、组件状态与动效示例。 - [交互设计规范](http://localhost:4173/Design.html):色板、图标、组件状态与动效示例。
- [设计方案总览](http://localhost:4173/design/index.html):01–05 五套设计、25 种主题、完整设计稿与多选审核入口;最新为「轻线纸境」。[方案目录](design/README.md)记录各套特点与共用规则。
- 修改端口:`PORT=4174 npm run dev`。 - 修改端口:`PORT=4174 npm run dev`。
## 游戏规则 ## 游戏规则
@@ -118,6 +119,8 @@ v0.5 的编排在 `src/production-plan.js`,选中配方在 `levels/v0.5-recipe
[Design.md](Design.md) 记录设计规范;[Design.html](Design.html) 内嵌图片与样式,可单文件离线分享。生成过程与提示词见 [素材说明](assets/GENERATION.md)。 [Design.md](Design.md) 记录设计规范;[Design.html](Design.html) 内嵌图片与样式,可单文件离线分享。生成过程与提示词见 [素材说明](assets/GENERATION.md)。
五套独立设计的 Agent 入口是 [design/README.md](design/README.md),共用接入规则见 [design/shared.md](design/shared.md)。每套在 `design/directions/<id>/` 下提供人工维护的 `design.md`、生成的 `tokens.json` 和预览 `index.html`;[manifest.json](design/manifest.json) 提供可编程读取的目录。使用方案时复用共享 JS/CSS,基础 Design.md 不包含各套的全部独立规则。
Cocos 示例复用规则、碰撞、渲染命令和图集;HTML 控件、文件导入导出与存储需替换成宿主接口。接入步骤见 [Cocos 移植指南](docs/cocos-port.md)。`wechat/HexTrail/` 是本仓库管理的 Cocos Creator 3.8.8 工程,后续将把当前游戏移植到该工程中。使用 Creator 打开该目录;工程自身的 `.gitignore` 排除缓存、本地配置和构建产物,源码与项目配置随主仓库提交。 Cocos 示例复用规则、碰撞、渲染命令和图集;HTML 控件、文件导入导出与存储需替换成宿主接口。接入步骤见 [Cocos 移植指南](docs/cocos-port.md)。`wechat/HexTrail/` 是本仓库管理的 Cocos Creator 3.8.8 工程,后续将把当前游戏移植到该工程中。使用 Creator 打开该目录;工程自身的 `.gitignore` 排除缓存、本地配置和构建产物,源码与项目配置随主仓库提交。
## 验证与已知限制 ## 验证与已知限制
+3 -3
View File
@@ -1,12 +1,12 @@
<!doctype html> <!doctype html>
<html lang="zh-CN"> <html lang="zh-CN">
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>HEX TRAILS · 色彩与离场设计</title><link rel="stylesheet" href="design/showcase.css"><link rel="stylesheet" href="design/notifications.css"><link rel="stylesheet" href="design/art-directions.css"><link rel="stylesheet" href="design/game-tools.css"></head> <head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>HEX TRAILS · 五套设计审核</title><link rel="stylesheet" href="design/showcase.css"><link rel="stylesheet" href="design/notifications.css"><link rel="stylesheet" href="design/art-directions.css"><link rel="stylesheet" href="design/game-tools.css"></head>
<body> <body>
<header class="masthead"><a href="index.html">HEX TRAILS<span>转转六角</span></a><span>DESIGN REVIEW / 04</span><a class="game-link" id="game-link" href="index.html">进入游戏 ↗</a></header> <header class="masthead"><a href="index.html">HEX TRAILS<span>转转六角</span></a><span>DESIGN REVIEW / 05</span><a class="game-link" id="game-link" href="index.html">进入游戏 ↗</a></header>
<nav class="quick-review" aria-label="快速切换设计与主题"><label>设计 <select id="quick-design"></select></label><label>主题 <select id="quick-theme"></select></label><a href="#direction-picker">多选与审核 ↑</a><a href="#item-study">道具设计 ↓</a></nav> <nav class="quick-review" aria-label="快速切换设计与主题"><label>设计 <select id="quick-design"></select></label><label>主题 <select id="quick-theme"></select></label><a href="#direction-picker">多选与审核 ↑</a><a href="#item-study">道具设计 ↓</a></nav>
<main> <main>
<section class="direction-review" aria-label="设计方案审核"> <section class="direction-review" aria-label="设计方案审核">
<div class="review-heading"><div><span class="eyebrow">ART DIRECTIONS / 4 × 5</span><h1>四种性格,同一段旅程。</h1><p>每套设计都有轻松、进阶、烧脑、挑战和每周挑战五种主题。切换查看,也可以同时保留多套。</p></div><a href="#preview">体验弯曲轨道 ↓</a></div> <div class="review-heading"><div><span class="eyebrow">ART DIRECTIONS / 5 × 5</span><h1>五种性格,同一段旅程。</h1><p>每套设计都有轻松、进阶、烧脑、挑战和每周挑战五种主题。切换查看,也可以同时保留多套。</p></div><a href="design/index.html">查看 01–05 设计总览 ↗</a></div>
<div id="direction-picker" class="direction-picker"></div> <div id="direction-picker" class="direction-picker"></div>
<div class="review-summary-bar"><p id="review-summary" aria-live="polite"></p><button id="export-review">导出审核记录 ↓</button></div> <div class="review-summary-bar"><p id="review-summary" aria-live="polite"></p><button id="export-review">导出审核记录 ↓</button></div>
<div id="direction-detail" class="direction-detail"></div> <div id="direction-detail" class="direction-detail"></div>
+11
View File
@@ -0,0 +1,11 @@
# 使用与维护设计方案
这是主游戏五套并列设计的入口。先读 [README.md](README.md),再读 [shared.md](shared.md) 和本次指定方案的 `directions/<id>/design.md`。可通过 [manifest.json](manifest.json) 发现全部 ID、规范、配色和预览路径;不必依赖先前会话。
- 设计 ID 固定为 `calm`、`vivid`、`toy`、`animals`、`paper`;方案编号为 01–05。每套都有相同的五个主题 ID。不得把设计、主题、关卡版本混成同一个参数。
- 使用方案时复用 `src/art-directions.js` 的 `artDirection` / `directionTheme` 和 `src/renderer.js` 的 `drawScene`。样式接入、颜色映射和跨引擎限制见 shared.md。
- 修改指定方案时,先核对该方案的 design.md。遵循用户已确认的共用规则:通知在步数下方、居中棋盘、大尺寸扁平道具、金色发光星星。`paper` 不画外围色标或棋子编号。
- `README.md`、`shared.md`、本文件及各套 `design.md` 是人工维护的文档源。`manifest.json`、各套 `tokens.json`、预览 HTML 由脚本生成;修改源 JS/CSS 和设计规则后重新构建,不手改生成结果。
- 每套仍是可单独使用、可同时保留的设计方向。除非任务明确选择默认方案,不因新增方案而替换 `calm` 默认值或其他审核选择。
- UI 任务以用户本次授权为边界。仅使用设计不需要改关卡、玩法、碰撞数学、存档或原生工程。不要为文档整理顺便改这些模块。
- 按 shared.md 的验证流程检查受影响内容。没有原生实机证据时,不报告 Cocos 已完成视觉接入或验收。
+80
View File
@@ -0,0 +1,80 @@
# HEX TRAILS · 设计系统入口
这是给设计审核者和 Agent 共用的主游戏设计入口。当前有 5 套并列方案,每套 5 个主题,共 25 种组合。默认方案是 01「柔和原色」,最新候选是 05「轻线纸境」;可同时保留多套。
人看 [设计总览](index.html) 与 [可操作审核页](../design-showcase.html)。Agent 从本文、[共用接入约定](shared.md) 和本次指定方案的 design.md 开始;可直接读 [manifest.json](manifest.json) 发现全部文件。根目录 [AGENTS.md](../AGENTS.md) 与本目录 [AGENTS.md](AGENTS.md) 提供路由和维护要求。
## 按任务找到入口
| 你要做什么 | 先读 | 然后做什么 |
| --- | --- | --- |
| 使用指定设计实现页面 | shared.md → 该套 design.md → tokens.json | 复用 directionTheme 和 drawScene,并接入对应 CSS |
| 调整某套造型或配色 | 该套 design.md → 文档列出的 JS/CSS 源 | 修改对应分支,同步文档,再生成配色与预览 |
| 比较与选择设计 | index.html | 同主题比较,再到审核页多选、记录意见 |
| 用其他引擎实现 | shared.md 跨引擎范围 → 该套 design.md | 复用绘制命令和几何,按浏览器参考验收 |
| 自动发现方案与颜色 | manifest.json → designs[].tokens | 路径从仓库根解析,schemaVersion 当前为 1 |
## 五套独立规范
| 编号 | ID | 方案 | Agent 规范 | 数据 | 预览 |
| --- | --- | --- | --- | --- | --- |
| 01 | calm | 柔和原色:哑光原版 | [design.md](directions/calm/design.md) | [tokens.json](directions/calm/tokens.json) | [查看](directions/calm/index.html) |
| 02 | vivid | 高彩游乐场:高饱和贴纸 | [design.md](directions/vivid/design.md) | [tokens.json](directions/vivid/tokens.json) | [查看](directions/vivid/index.html) |
| 03 | toy | 积木轨道屋:塑胶积木 | [design.md](directions/toy/design.md) | [tokens.json](directions/toy/tokens.json) | [查看](directions/toy/index.html) |
| 04 | animals | 动物弯弯镇:动物角色 | [design.md](directions/animals/design.md) | [tokens.json](directions/animals/tokens.json) | [查看](directions/animals/index.html) |
| 05 | paper | 轻线纸境:细线纸面 | [design.md](directions/paper/design.md) | [tokens.json](directions/paper/tokens.json) | [查看](directions/paper/index.html) |
设计 ID、主题 ID 和关卡版本各自独立。每套都支持 `lagoon`(轻松)、`apricot`(进阶)、`lavender`(烧脑)、`rose`(挑战)、`midnight`(每周挑战);显示名可以不同,ID 不变。各主题名称、色值与有序色板从 tokens.json 读取。
## 文件架构与来源
```text
design/
├── AGENTS.md # Agent 工作入口,人工维护
├── README.md # 本索引,人工维护
├── shared.md # 共用规则、接入、验证与限制,人工维护
├── manifest.json # 全部方案与文件路径,生成
├── index.html # 人看的总览,生成
├── examples/
│ └── render-design.mjs # 可执行的无 DOM 接入示例
├── directions/
│ ├── calm/ # 其余 vivid / toy / animals / paper 结构相同
│ │ ├── design.md # 本套设计规范,人工维护,构建不覆盖
│ │ ├── tokens.json # 12 色库、5 主题与 4/7/9/12 色板,生成
│ │ └── index.html # 人看的完整设计稿,生成
│ ├── calm.html # 兼容旧 URL,跳转到 calm/index.html
│ └── calm.md # 兼容旧文档入口,指向 calm/design.md
├── art-directions.css # 五套控件的样式源
├── game-tools.css # 共用扁平道具与布局源
└── tokens.json # 基础语义色导出,不是五套方案全集
```
设计颜色定义仍在 [src/art-directions.js](../src/art-directions.js) / [src/design-tokens.js](../src/design-tokens.js),几何仍在 [src/renderer.js](../src/renderer.js) / [src/token-art.js](../src/token-art.js)。没有将运行时代码复制到每套目录。各 design.md 解释意图、精确画法、控件差异、不可丢失的特征和验收点;tokens.json 给 Agent 提供当前数值快照。
所有生成 JSON 中 `pathBase` 为 `repository-root`;文件路径从仓库根目录解析。它与 Markdown 内按当前文档位置解析的相对链接不同。manifest 的版本号描述数据格式,不代表第几套设计。新增字段可保持同一版本;改变字段含义或移除字段时升级版本并更新消费者。
## 快速验证一次接入
在仓库根目录执行,Node.js 20+,无需安装依赖:
```sh
node design/examples/render-design.mjs paper lagoon 7
```
它调用共享主题与渲染器,输出设计 ID、主题 ID、色数、棋盘格数、色板和绘制命令数。可更换为 `animals midnight 12`。未知 ID 或不支持的色数会报错,避免 Agent 拼错 ID 后默默使用默认设计。此例验证无 DOM 几何接入,不代替 PNG、CSS 或实机视觉验收。浏览器 Canvas 示例在 shared.md 中。
## 修改、生成与校验
```sh
npm run design
node --test tests/design-packages.test.js tests/art-directions.test.js tests/themes.test.js
node scripts/build.mjs
```
- 规范源:本目录 AGENTS.md、README.md、shared.md、各套 design.md,直接编辑且不会被生成脚本覆盖。
- 实现源:JS/CSS、已接受的图集;在本次指定的方案范围内修改。
- 生成物:manifest、各套 tokens.json、index.html 及旧入口;不要只改生成物,下一次构建会重建。
- 新增方案:在 ART_DIRECTIONS 注册稳定 ID,新增 `directions/<id>/design.md`,更新本索引与相关验证,再运行构建。缺少规范时构建会失败。
- 校验覆盖文件可发现性、链接、配色导出与运行时一致性,以及可执行示例。视觉与原生范围按 shared.md 验收。
旧的 `design/directions/<id>.html` 和 `<id>.md` 入口继续可用;新的代码和文档请引用独立目录。根目录 Design.md / Design.html 保留基础组件与历史设计说明,不作为五套独立方案的唯一规范。实验室中的其他玩法原型各有自己的文档,不在这五套主游戏设计的 manifest 中。
+17 -1
View File
@@ -1,5 +1,5 @@
.direction-review{padding:52px 0 24px}.review-heading{display:flex;align-items:end;justify-content:space-between;gap:30px}.review-heading h1{font-size:clamp(30px,4vw,48px);letter-spacing:-1.5px;margin:14px 0}.review-heading p{max-width:730px;color:#56687A}.review-heading>a{font-size:12px;white-space:nowrap;padding-bottom:22px} .direction-review{padding:52px 0 24px}.review-heading{display:flex;align-items:end;justify-content:space-between;gap:30px}.review-heading h1{font-size:clamp(30px,4vw,48px);letter-spacing:-1.5px;margin:14px 0}.review-heading p{max-width:730px;color:#56687A}.review-heading>a{font-size:12px;white-space:nowrap;padding-bottom:22px}
.direction-picker{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:16px;margin:28px 0 18px}.direction-card{border:1px solid #273b5020;border-radius:18px;overflow:hidden;background:white}.direction-switch{display:block;background:none;padding:0;width:100%;text-align:left;color:#273B50}.direction-switch canvas{display:block;width:100%;height:auto}.direction-switch[aria-pressed=true]{box-shadow:inset 0 -3px #6652B8}.direction-card:has([aria-pressed=true]){outline:2px solid #6652B8;outline-offset:3px}.direction-card-title{display:flex;gap:8px;align-items:center;justify-content:space-between;padding:14px 12px;font-weight:650;font-size:13px}.direction-card-title small{font-weight:400;font-size:10px;color:#56687A}.keep-design{display:flex;align-items:center;gap:8px;border-top:1px solid #273b5010;padding:12px;font-size:12px;cursor:pointer}.keep-design input{width:17px;height:17px;accent-color:#6652B8}.review-summary-bar{display:flex;align-items:center;justify-content:space-between;gap:20px;font-size:12px}.review-summary-bar p{font-size:12px}.review-summary-bar button,.sheet-link{background:#273B50;color:white;border-radius:10px;padding:12px 16px;font-size:12px;display:inline-block}.direction-detail{margin-top:24px;border-top:1px solid #273b5020;border-bottom:1px solid #273b5020;padding:30px 0;display:grid;grid-template-columns:1.25fr 1fr;gap:60px}.direction-detail h2{font-size:28px;margin:10px 0}.direction-detail p,.direction-detail li{font-size:13px;line-height:1.9}.direction-detail ul{padding-left:18px;color:#56687A;margin-bottom:22px}.review-note{font-size:13px;display:flex;flex-direction:column;gap:12px;padding-top:4px}.review-note textarea{resize:vertical;width:100%;min-height:110px;border:1px solid #273b5030;border-radius:12px;padding:14px;background:white;font:inherit;color:#273B50}.review-note small{font-size:11px;color:#56687A}.intro{padding-top:30px}.intro h1{font-size:34px}.direction-review~.intro .intro-note{max-width:520px} .direction-picker{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:16px;margin:28px 0 18px}.direction-card{border:1px solid #273b5020;border-radius:18px;overflow:hidden;background:white}.direction-switch{display:block;background:none;padding:0;width:100%;text-align:left;color:#273B50}.direction-switch canvas{display:block;width:100%;height:auto}.direction-switch[aria-pressed=true]{box-shadow:inset 0 -3px #6652B8}.direction-card:has([aria-pressed=true]){outline:2px solid #6652B8;outline-offset:3px}.direction-card-title{display:flex;gap:8px;align-items:center;justify-content:space-between;padding:14px 12px;font-weight:650;font-size:13px}.direction-card-title small{font-weight:400;font-size:10px;color:#56687A}.keep-design{display:flex;align-items:center;gap:8px;border-top:1px solid #273b5010;padding:12px;font-size:12px;cursor:pointer}.keep-design input{width:17px;height:17px;accent-color:#6652B8}.review-summary-bar{display:flex;align-items:center;justify-content:space-between;gap:20px;font-size:12px}.review-summary-bar p{font-size:12px}.review-summary-bar button,.sheet-link{background:#273B50;color:white;border-radius:10px;padding:12px 16px;font-size:12px;display:inline-block}.direction-detail{margin-top:24px;border-top:1px solid #273b5020;border-bottom:1px solid #273b5020;padding:30px 0;display:grid;grid-template-columns:1.25fr 1fr;gap:60px}.direction-detail h2{font-size:28px;margin:10px 0}.direction-detail p,.direction-detail li{font-size:13px;line-height:1.9}.direction-detail ul{padding-left:18px;color:#56687A;margin-bottom:22px}.review-note{font-size:13px;display:flex;flex-direction:column;gap:12px;padding-top:4px}.review-note textarea{resize:vertical;width:100%;min-height:110px;border:1px solid #273b5030;border-radius:12px;padding:14px;background:white;font:inherit;color:#273B50}.review-note small{font-size:11px;color:#56687A}.intro{padding-top:30px}.intro h1{font-size:34px}.direction-review~.intro .intro-note{max-width:520px}
body[data-design=vivid] .phone{border:3px solid #30264C;box-shadow:8px 8px #30264C;border-radius:18px}body[data-design=vivid] .icon-button{background:#4A24E8;border:2px solid #30264C;border-radius:8px;box-shadow:3px 3px #30264C}body[data-design=vivid] .phone-top strong{font-weight:900;letter-spacing:-.6px}body[data-design=vivid] .game-notice{border:2px solid #30264C;border-radius:8px;box-shadow:3px 3px #30264C} body[data-design=vivid] .phone{border:3px solid #30264C;box-shadow:8px 8px #30264C;border-radius:18px}body[data-design=vivid] .icon-button{background:#4A24E8;border:2px solid #30264C;border-radius:8px;box-shadow:3px 3px #30264C}body[data-design=vivid] .phone-top strong{font-weight:900;letter-spacing:-.6px}body[data-design=vivid] .game-notice{border:2px solid #30264C;border-radius:8px;box-shadow:3px 3px #30264C}
body[data-design=toy] .phone{border:8px solid #F9EDCF;border-radius:42px;box-shadow:0 12px 0 #B5A68D,0 20px 30px #273b501f,inset 0 3px 3px #FFFFFF90}body[data-design=toy] .icon-button{background:#4279B7;border-radius:15px;box-shadow:0 4px #244F80,inset 0 2px #82ACD2}body[data-design=toy] .game-notice{background:#FFF9EB;border:2px solid #D8CBB0;box-shadow:0 3px #C4B598} body[data-design=toy] .phone{border:8px solid #F9EDCF;border-radius:42px;box-shadow:0 12px 0 #B5A68D,0 20px 30px #273b501f,inset 0 3px 3px #FFFFFF90}body[data-design=toy] .icon-button{background:#4279B7;border-radius:15px;box-shadow:0 4px #244F80,inset 0 2px #82ACD2}body[data-design=toy] .game-notice{background:#FFF9EB;border:2px solid #D8CBB0;box-shadow:0 3px #C4B598}
body[data-design=animals] .phone{border:3px solid #718763;border-radius:40px 40px 28px 28px;box-shadow:0 6px 0 #71876330}body[data-design=animals] .icon-button{background:#54795C;border-radius:16px 16px 12px 12px;box-shadow:0 3px #36523C}body[data-design=animals] .game-notice{background:#FFFCED;border:1px solid #71876380;border-radius:18px}.phone canvas{min-width:0} body[data-design=animals] .phone{border:3px solid #718763;border-radius:40px 40px 28px 28px;box-shadow:0 6px 0 #71876330}body[data-design=animals] .icon-button{background:#54795C;border-radius:16px 16px 12px 12px;box-shadow:0 3px #36523C}body[data-design=animals] .game-notice{background:#FFFCED;border:1px solid #71876380;border-radius:18px}.phone canvas{min-width:0}
@@ -12,3 +12,19 @@ body[data-design=toy]:not(.editing) .game-shell .game-icon{filter:drop-shadow(0
body[data-design=animals]:not(.editing) .game-shell .game-icon{filter:sepia(.25) hue-rotate(320deg)} body[data-design=animals]:not(.editing) .game-shell .game-icon{filter:sepia(.25) hue-rotate(320deg)}
.quick-review{position:sticky;top:0;z-index:10;display:flex;align-items:center;justify-content:center;gap:22px;padding:10px 22px;background:#F7F6F2F5;border-bottom:1px solid #273b501c;backdrop-filter:blur(12px);font-size:12px}.quick-review label{display:flex;align-items:center;gap:9px}.quick-review select{font:inherit;color:#273B50;background:white;border:1px solid #273b5033;border-radius:8px;min-height:36px;padding:5px 10px}.quick-review a{font-size:11px}.direction-picker,.phone{scroll-margin-top:80px} .quick-review{position:sticky;top:0;z-index:10;display:flex;align-items:center;justify-content:center;gap:22px;padding:10px 22px;background:#F7F6F2F5;border-bottom:1px solid #273b501c;backdrop-filter:blur(12px);font-size:12px}.quick-review label{display:flex;align-items:center;gap:9px}.quick-review select{font:inherit;color:#273B50;background:white;border:1px solid #273b5033;border-radius:8px;min-height:36px;padding:5px 10px}.quick-review a{font-size:11px}.direction-picker,.phone{scroll-margin-top:80px}
@media(max-width:500px){.quick-review{gap:10px;padding:8px 10px}.quick-review label{gap:5px}.quick-review select{max-width:130px;font-size:11px}.quick-review a{font-size:10px}.review-heading h1{font-size:28px}} @media(max-width:500px){.quick-review{gap:10px;padding:8px 10px}.quick-review label{gap:5px}.quick-review select{max-width:130px;font-size:11px}.quick-review a{font-size:10px}.review-heading h1{font-size:28px}}
/* Paper Routes carries the light, flat drawing language of the variants lab. */
body[data-design=paper] .phone{border:1px solid #CBD7DD;border-radius:28px;box-shadow:none}
body[data-design=paper] .icon-button{background:#607B90;border:1px solid #FFFFFFB8;border-radius:12px;box-shadow:none}
body[data-design=paper] .game-notice{background:#FAFBF7;color:#506674;border:1px solid #D3DFE3;box-shadow:none}
body[data-design=paper] .rotations button>span,body[data-design=paper]:not(.editing) .rotate-button>.ui-icon{filter:brightness(0) saturate(100%) invert(43%) sepia(12%) saturate(987%) hue-rotate(161deg) brightness(94%) contrast(88%)}
body[data-design=paper][data-theme=midnight] .rotations button>span,body[data-design=paper][data-theme=midnight]:not(.editing) .rotate-button>.ui-icon{filter:none}
body[data-design=paper] .game-tool{--item-color:#477FC0;--item-border:1px solid #FFFFFFCC;--item-radius:50%}
body[data-design=paper] .game-tool[data-tool=restart]{--item-color:#D66C4D}
body[data-design=paper] .game-tool[data-tool=hint]{--item-color:#B58943;--item-icon-filter:none}
.sheet-reference{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:40px;align-items:center;margin:0 0 48px;padding:28px;background:#EAF0F3;border-radius:24px}
.sheet-reference canvas{display:block;width:100%;height:auto}.sheet-reference h2{font-size:28px;margin:14px 0}.sheet-reference p{font-size:13px;color:#506674}.sheet-reference a{font-size:12px;text-decoration:underline;text-underline-offset:4px}
.reference-spec{display:grid;grid-template-columns:1fr auto;gap:12px;font-size:12px;padding:18px 0;border-top:1px solid #CBD7DD;border-bottom:1px solid #CBD7DD}.reference-spec dt,.reference-spec dd{margin:0}.reference-spec dd{font-family:ui-monospace,monospace;color:#506674}
@media(max-width:650px){.sheet-reference{grid-template-columns:1fr;gap:20px;padding:18px}.sheet-reference h2{font-size:24px}}
.sheet-nav{gap:16px;align-items:center;flex-wrap:wrap}.sheet-family-nav{display:flex;flex-wrap:wrap;gap:8px;padding-top:20px}.sheet-family-nav a{font-size:11px;min-height:40px;padding:11px 13px;border:1px solid #CBD7DD;border-radius:10px}.sheet-family-nav a[aria-current=page]{background:#294D60;color:#FFFFFF;border-color:#294D60}
.sheet-agent-links{display:flex;flex-wrap:wrap;gap:18px;margin-top:18px;font-size:11px;color:#506674}.sheet-agent-links a{text-decoration:underline;text-underline-offset:4px}
+12
View File
@@ -0,0 +1,12 @@
:root{font-family:'PingFang SC','Microsoft YaHei',sans-serif;color:#273B50;background:#F7F8F5;font-synthesis:none}
*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0}a{color:inherit;text-decoration:none}button{font:inherit;cursor:pointer}a:focus-visible,button:focus-visible{outline:3px solid #6652B8;outline-offset:5px}p{font-size:14px;line-height:1.85;color:#56687A}h1,h2,h3{letter-spacing:-.6px}h2{font-size:28px;margin:12px 0}main,.catalog-nav,footer{max-width:1320px;margin:auto;padding-left:40px;padding-right:40px}
.catalog-nav{display:flex;justify-content:space-between;align-items:center;gap:20px;padding-top:24px;padding-bottom:24px;border-bottom:1px solid #D7DEDF;font-size:13px}.brand{font-size:19px;font-weight:750}.brand span{font-size:11px;font-weight:500;margin-left:14px}.eyebrow{font-size:10px;letter-spacing:2px;color:#56687A;font-weight:650}
.catalog-intro{display:grid;grid-template-columns:1fr 310px;align-items:center;gap:70px;padding:62px 0 50px}.catalog-intro h1{font-size:clamp(38px,5vw,62px);line-height:1.18;margin:18px 0}.catalog-intro p{margin-bottom:0}.latest-design{display:flex;flex-direction:column;gap:14px;border-left:2px solid #A9BDC7;padding:8px 0 8px 26px}.latest-design>span{font-size:12px;color:#56687A}.latest-design strong{font-size:25px}.latest-design small{font-size:13px;line-height:1.85;color:#56687A}.latest-design b{font-size:12px;font-weight:600}
.compare-heading{display:flex;justify-content:space-between;align-items:center;gap:22px;border-top:1px solid #D7DEDF;padding-top:32px}.compare-heading p{margin-bottom:0}.compare-heading>a{white-space:nowrap;font-size:12px}.catalog-theme-picker{display:flex;gap:8px;flex-wrap:wrap;margin:24px 0 12px}.catalog-theme-picker button{border:1px solid #BCC9CE;border-radius:24px;min-height:44px;padding:10px 20px;color:#273B50;background:transparent;font-size:13px}.catalog-theme-picker button[aria-pressed=true]{color:#FFF;background:#294D60;border-color:#294D60}.comparison-status{font-size:11px;margin:0 0 20px}
.catalog-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:24px}.catalog-card{border:1px solid #D7DEDF;background:#FFF;border-radius:20px;overflow:hidden;scroll-margin-top:24px}.card-preview{position:relative;padding:20px 16px 16px}.design-number{font-size:11px;letter-spacing:1px}.card-preview canvas{display:block;width:100%;height:auto;margin-top:4px}.preview-theme{display:block;text-align:center;font-size:12px}.card-body{padding:24px;display:flex;flex-direction:column}.card-body h3{font-size:22px;margin:10px 0 0;display:flex;align-items:center;gap:10px}.card-body h3 small{font-size:10px;letter-spacing:0;font-weight:500;background:#EAF0F3;border-radius:6px;padding:4px 7px}.card-body p{font-size:13px;min-height:72px;margin:12px 0 20px}.card-actions{display:flex;flex-wrap:wrap;gap:12px;align-items:center;font-size:12px}.card-actions a:first-child{background:#294D60;color:white;padding:12px 14px;border-radius:10px}.card-actions a:last-child{padding:10px 0}
.theme-map{padding-top:64px;scroll-margin-top:24px}.theme-matrix{border-top:1px solid #D7DEDF;margin-top:26px}.theme-family{display:grid;grid-template-columns:170px 1fr;align-items:center;border-bottom:1px solid #D7DEDF;padding:20px 0;gap:20px}.theme-family h3{font-size:14px;margin:0}.theme-family h3 span{font-size:11px;color:#56687A;margin-right:8px}.theme-family>div{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:16px}.theme-entry{display:flex;gap:10px;align-items:center;min-width:0;padding:4px 0}.theme-entry i{width:28px;height:38px;border:1px solid #273B5020;border-radius:7px;flex-shrink:0}.theme-entry small,.theme-entry strong{display:block}.theme-entry small{font-size:10px;color:#56687A;margin-bottom:5px}.theme-entry strong{font-size:12px;font-weight:500;line-height:1.5}.theme-entry b{display:none;font-size:12px;font-weight:400}
.catalog-notes{display:grid;grid-template-columns:250px 1fr;gap:24px;padding:54px 0}.catalog-notes ul{margin:0;padding-left:18px;font-size:13px;line-height:2.2;color:#56687A}.catalog-notes>a{grid-column:2;font-size:12px;text-decoration:underline;text-underline-offset:5px}footer{display:flex;justify-content:space-between;gap:20px;padding-top:24px;padding-bottom:30px;border-top:1px solid #D7DEDF;font-size:11px;color:#56687A}
@media(max-width:1050px){.catalog-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.catalog-intro{gap:36px}.theme-family{grid-template-columns:1fr;gap:16px}}
@media(max-width:650px){main,.catalog-nav,footer{padding-left:22px;padding-right:22px}.brand{font-size:17px}.brand span{display:none}.catalog-nav{font-size:12px}.catalog-intro{grid-template-columns:1fr;gap:28px;padding:38px 0}.catalog-intro h1{font-size:40px}.latest-design{padding-left:18px}.latest-design small{display:none}.latest-design{gap:10px}.compare-heading{display:block}.compare-heading>a{display:inline-block;margin-top:16px}h2{font-size:24px}.catalog-theme-picker{gap:6px}.catalog-theme-picker button{padding:9px 13px;font-size:12px}.catalog-grid{grid-template-columns:1fr;gap:22px}.card-body p{min-height:0}.theme-map{padding-top:44px}.theme-family>div{grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}.theme-entry b{display:block;margin-left:auto}.theme-entry{padding-right:6px}.catalog-notes{grid-template-columns:1fr;gap:16px;padding:40px 0}.catalog-notes>a{grid-column:auto}footer{flex-direction:column;gap:12px}}
@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}}
.catalog-nav nav{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:18px;font-size:12px}@media(max-width:650px){.catalog-nav nav{gap:10px;font-size:10px}}
File diff suppressed because one or more lines are too long
+3 -25
View File
@@ -1,27 +1,5 @@
# 动物弯弯镇 # 04 · 动物弯弯镇
猫咪、小熊、兔子与青蛙沿轨道出游。用表情与耳朵增加性格,方向箭头独立保留。 本文件为旧入口。完整、人工维护的 Agent 规范已迁移到 [animals/design.md](animals/design.md)。
## 设计规则 [设计稿](animals/index.html) · [配色数据](animals/tokens.json) · [Agent 接入说明](../README.md)
- 圆脸、短耳与小表情,外形保持在六角范围内
- 箭头留在脸部下方,不靠动物朝向猜方向
- 草地、木棕与奶油毛色,搭配森林小径
## 五种主题
| 难度 | 名称 | 环境色 |
| --- | --- | --- |
| 轻松 | 青草野餐 | #A9CD69 |
| 进阶 | 橡果秋游 | #E7B947 |
| 烧脑 | 溪边捉迷藏 | #72BEB2 |
| 挑战 | 红土探险 | #C67B47 |
| 每周挑战 | 萤火森林 | #183F36 |
## 游戏道具与布局
- 撤销、重来、提示使用独立彩色底座,64–68px,图标 32px,标签 13px。顶部导航保持紧凑。道具使用纯色平面,不使用阴影、立体底边或渐变高光。
- 右上角预留次数或“▶ AD”角标。设计稿展示“撤销 3 次”和“广告获取提示”,仅为样式,不启用真实库存或广告逻辑。
- 棋盘在通知区与独立旋转区之间垂直居中。旋转区和道具区占据真实布局行,不覆盖棋盘。
此方案使用独立的棋子造型、轨道材质和控件样式,共用现有弯曲轨道玩法。审核页支持同时保留多套方案。
+52
View File
@@ -0,0 +1,52 @@
# 04 · 动物弯弯镇
设计 ID:`animals` · 渲染风格:`animals` · 英文名:PAW PARADE。卡通动物候选方案,可与其他方案同时保留。
[Agent 接入约定](../../shared.md) · [配色数据](tokens.json) · [完整设计稿](index.html) · [试玩与审核](../../../design-showcase.html?design=animals)
## 设计目标
猫、熊、兔与青蛙沿轨道出游。颜色来自毛色、草地、木棕和奶油色,保留自然感。表情用来增加性格;玩家通过独立箭头判断方向,不靠动物脸朝向猜规则。
## 棋盘与棋子怎么画
复用 [renderer.js](../../../src/renderer.js) 的非 paper 棋盘和 [token-art.js](../../../src/token-art.js) 的 animals 分支。r = 格半径 s × 0.985。
| 部件 | 当前实现 |
| --- | --- |
| 分格/轨道 | 分格 max(1, s × 0.024),外缘 max(4, s × 0.16),中心线 max(2.5, s × 0.10) |
| 六角底托 | 半径 r × 0.94,奶油白 #FFFCED,浅绿边 #526F5744、线宽 r × 0.035 |
| 动物种类 | 经主题映射后的色库索引 % 4:0 猫、1 熊、2 兔、3 蛙 |
| 脸部 | 圆脸半径 r × 0.51,中心上移 r × 0.04;耳朵用三角、圆或采样椭圆 |
| 表情 | 深色 #263A3A 眼睛与嘴,#EF958B 腮红;不增加数字或文字 |
| 方向底 | 下方 y + r × 0.43 的白圆,半径 r × 0.36 |
| 方向箭头 | #263A3A 实心箭头,尺度 r × 0.44,位于白圆内 |
动物轮廓和脸保持正立,方向箭头独立旋转。tokens.json 中 pigments.arrow 是共享颜色 token;**animals 分支的实际箭头固定为 #263A3A,底色是白圆**,不要用颜料色库的 arrow 覆盖脸下方向区。
角色种类是表现映射,不是游戏功能或阵营。初始棋子索引决定主题色,再决定种类;移除棋子后不可重排剩余动物。全部造型为多边形、线与圆,不需要另生成动物 PNG。
## 五种主题
| 主题 ID | 阶段 | 名称 | 独立用色方向 |
| --- | --- | --- | --- |
| lagoon | 轻松 | 青草野餐 | 草地绿、奶油底与动物毛色 |
| apricot | 进阶 | 橡果秋游 | 秋叶黄、橡果棕与麦芽金 |
| lavender | 烧脑 | 溪边捉迷藏 | 溪水青、苔绿与暖棕 |
| rose | 挑战 | 红土探险 | 红土环境与沙色小径 |
| midnight | 每周挑战 | 萤火森林 | 深森林绿与萤火浅黄 |
完整配色和每主题 4/7/9/12 色有序 palette 见 tokens.json,源为 [art-directions.js](../../../src/art-directions.js)。不要把原版粉紫糖果配色直接套在动物脸上。
## 控件与边界
顶部控件与通知延续绿、奶油色与圆润边界。底部道具为苔绿/木棕/黄的纯色底座,圆角比例 44% 44% 36% 36%,3px 奶油边;有轻微点线装饰,无投影或立体厚度。角标与通关星星规则见 shared.md。
## 验收与接入
使用 `artDirection('animals')` 和 `directionTheme(direction, themeId, colorCount)`。
- 在最小棋子尺寸检查耳朵、脸、白圆与箭头,不因表情抢走方向信息。
- 检查六个方向、转弯移动、撤销后的动物种类与配色稳定。
- 夜间主题中浅色角色与绿板面分离,方向仍靠箭头表达。
- 原生适配需核对 circle 与 polygon 的图层顺序;现有 Cocos 示例未完成此套角色的实机验收。
File diff suppressed because one or more lines are too long
+469
View File
@@ -0,0 +1,469 @@
{
"schemaVersion": 1,
"generated": true,
"generatedBy": "scripts/build-art-directions.mjs",
"pathBase": "repository-root",
"designId": "animals",
"artStyle": "animals",
"sources": {
"directions": "src/art-directions.js",
"baseTokens": "src/design-tokens.js",
"renderer": "src/renderer.js",
"tokenDrawing": "src/token-art.js",
"componentStyles": "design/art-directions.css",
"itemStyles": "design/game-tools.css"
},
"accent": "#396D51",
"documentBackground": "#FFFCF1",
"sharedSemanticColors": {
"background": "#A8BECF",
"board": "#F2EEE5",
"boardGrid": "#89867D",
"track": "#435F78",
"trackHalo": "#F2EEE5",
"surface": "#F7F6F2",
"textPrimary": "#273B50",
"textSecondary": "#314A5F",
"primary": "#6652B8",
"onPrimary": "#FFFFFF",
"secondary": "#E4EAF0",
"utility": "#52697E",
"onUtility": "#FFFFFF",
"selection": "#DFD9F3",
"onSelection": "#3F327A",
"disabled": "#CBD4DD",
"onDisabled": "#526579",
"input": "#FFFFFF",
"reward": "#B9760C",
"warning": "#A44825",
"warningSurface": "#F9E3D2",
"scrim": "#273B507A",
"focus": "#3F327A",
"tileBlue": "#2392FD",
"tilePink": "#FC3E80",
"tileYellow": "#FDBC28",
"tilePurple": "#874FFD",
"arrowLight": "#FFFFFF",
"arrowDark": "#273B50"
},
"assets": {
"tileAtlas": null,
"uiIconAtlas": "assets/ui-icons-generated.png"
},
"pigments": [
{
"index": 0,
"name": "姜黄猫",
"color": "#D9934F",
"arrow": "#172D39"
},
{
"index": 1,
"name": "栗棕熊",
"color": "#A4683B",
"arrow": "#FFFFFF"
},
{
"index": 2,
"name": "麦芽兔",
"color": "#E1C58B",
"arrow": "#172D39"
},
{
"index": 3,
"name": "苔绿蛙",
"color": "#6C9D40",
"arrow": "#172D39"
},
{
"index": 4,
"name": "奶橘猫",
"color": "#F1B869",
"arrow": "#172D39"
},
{
"index": 5,
"name": "可可熊",
"color": "#73503F",
"arrow": "#FFFFFF"
},
{
"index": 6,
"name": "奶油兔",
"color": "#F1DCC1",
"arrow": "#172D39"
},
{
"index": 7,
"name": "溪绿蛙",
"color": "#448A67",
"arrow": "#172D39"
},
{
"index": 8,
"name": "榛果猫",
"color": "#B78157",
"arrow": "#172D39"
},
{
"index": 9,
"name": "赤陶熊",
"color": "#BD784F",
"arrow": "#172D39"
},
{
"index": 10,
"name": "雪白兔",
"color": "#F4E5CF",
"arrow": "#172D39"
},
{
"index": 11,
"name": "嫩叶蛙",
"color": "#87B76B",
"arrow": "#172D39"
}
],
"themes": [
{
"id": "lagoon",
"label": "轻松",
"name": "青草野餐",
"background": "#A9CD69",
"ink": "#12202A",
"materials": {
"board": "#FFF2D2",
"boardGrid": "#A9AC80",
"track": "#527345",
"trackHalo": "#FFF2D2"
},
"anchorPalette": [
0,
1,
2,
3,
6,
7,
10
],
"palettes": {
"4": [
0,
1,
2,
3
],
"7": [
0,
1,
2,
3,
6,
7,
10
],
"9": [
0,
1,
2,
3,
6,
7,
10,
4,
5
],
"12": [
0,
1,
2,
3,
6,
7,
10,
4,
5,
8,
9,
11
]
},
"contrast": {
"sceneText": 9.183048868633442,
"trackOnBoard": 4.8456513485094925
}
},
{
"id": "apricot",
"label": "进阶",
"name": "橡果秋游",
"background": "#E7B947",
"ink": "#12202A",
"materials": {
"board": "#FFEDB5",
"boardGrid": "#B6A076",
"track": "#79572E",
"trackHalo": "#FFEDB5"
},
"anchorPalette": [
1,
4,
8,
5,
2,
6,
11
],
"palettes": {
"4": [
1,
4,
8,
5
],
"7": [
1,
4,
8,
5,
2,
6,
11
],
"9": [
1,
4,
8,
5,
2,
6,
11,
0,
3
],
"12": [
1,
4,
8,
5,
2,
6,
11,
0,
3,
7,
9,
10
]
},
"contrast": {
"sceneText": 9.028864535418991,
"trackOnBoard": 5.607739454320704
}
},
{
"id": "lavender",
"label": "烧脑",
"name": "溪边捉迷藏",
"background": "#72BEB2",
"ink": "#12202A",
"materials": {
"board": "#ECF1D8",
"boardGrid": "#9CA987",
"track": "#3B7262",
"trackHalo": "#ECF1D8"
},
"anchorPalette": [
7,
3,
6,
10,
0,
4,
9
],
"palettes": {
"4": [
7,
3,
6,
10
],
"7": [
7,
3,
6,
10,
0,
4,
9
],
"9": [
7,
3,
6,
10,
0,
4,
9,
1,
2
],
"12": [
7,
3,
6,
10,
0,
4,
9,
1,
2,
5,
8,
11
]
},
"contrast": {
"sceneText": 7.6821442549225605,
"trackOnBoard": 4.812692332706211
}
},
{
"id": "rose",
"label": "挑战",
"name": "红土探险",
"background": "#C67B47",
"ink": "#12202A",
"materials": {
"board": "#F6DCB2",
"boardGrid": "#B19169",
"track": "#684835",
"trackHalo": "#F6DCB2"
},
"anchorPalette": [
8,
5,
4,
2,
7,
10,
1
],
"palettes": {
"4": [
8,
5,
4,
2
],
"7": [
8,
5,
4,
2,
7,
10,
1
],
"9": [
8,
5,
4,
2,
7,
10,
1,
0,
3
],
"12": [
8,
5,
4,
2,
7,
10,
1,
0,
3,
6,
9,
11
]
},
"contrast": {
"sceneText": 4.997261835536376,
"trackOnBoard": 6.154627594688633
}
},
{
"id": "midnight",
"label": "每周挑战",
"name": "萤火森林",
"background": "#183F36",
"ink": "#FFFFFF",
"materials": {
"board": "#355C43",
"boardGrid": "#879969",
"track": "#E6E9AE",
"trackHalo": "#355C43"
},
"anchorPalette": [
6,
10,
2,
4,
11,
0,
9
],
"palettes": {
"4": [
6,
10,
2,
4
],
"7": [
6,
10,
2,
4,
11,
0,
9
],
"9": [
6,
10,
2,
4,
11,
0,
9,
1,
3
],
"12": [
6,
10,
2,
4,
11,
0,
9,
1,
3,
5,
7,
8
]
},
"contrast": {
"sceneText": 11.64654981527941,
"trackOnBoard": 6.018692953375532
}
}
]
}
File diff suppressed because one or more lines are too long
+3 -25
View File
@@ -1,27 +1,5 @@
# 柔和原色 # 01 · 柔和原色
低饱和环境与平面六角棋子,让注意力留在轨道和方向。 本文件为旧入口。完整、人工维护的 Agent 规范已迁移到 [calm/design.md](calm/design.md)。
## 设计规则 [设计稿](calm/index.html) · [配色数据](calm/tokens.json) · [Agent 接入说明](../README.md)
- 哑光色块,克制装饰
- 蓝灰细轨道,保持视觉留白
- 金色奖励,柔和浮现
## 五种主题
| 难度 | 名称 | 环境色 |
| --- | --- | --- |
| 轻松 | 薄荷浅湾 | #C5E4DF |
| 进阶 | 杏色午后 | #F1D7B8 |
| 烧脑 | 鸢尾花园 | #D8D0EB |
| 挑战 | 玫瑰暮色 | #E7C3CE |
| 每周挑战 | 午夜极光 | #293956 |
## 游戏道具与布局
- 撤销、重来、提示使用独立彩色底座,64–68px,图标 32px,标签 13px。顶部导航保持紧凑。道具使用纯色平面,不使用阴影、立体底边或渐变高光。
- 右上角预留次数或“▶ AD”角标。设计稿展示“撤销 3 次”和“广告获取提示”,仅为样式,不启用真实库存或广告逻辑。
- 棋盘在通知区与独立旋转区之间垂直居中。旋转区和道具区占据真实布局行,不覆盖棋盘。
此方案使用独立的棋子造型、轨道材质和控件样式,共用现有弯曲轨道玩法。审核页支持同时保留多套方案。
+51
View File
@@ -0,0 +1,51 @@
# 01 · 柔和原色
设计 ID:`calm` · 渲染风格:`classic` · 英文名:QUIET TRAILS。这是当前默认的基础方案,可与其他候选方案同时保留。
[Agent 接入约定](../../shared.md) · [配色数据](tokens.json) · [完整设计稿](index.html) · [试玩与审核](../../../design-showcase.html?design=calm)
## 设计目标
低饱和环境、暖白底盘和哑光六角棋子,让注意力集中在弯曲轨道与方向。原有棋子是较鲜明的颜色;“柔和”主要指背景和整体气氛,不代表把全部棋子调成灰色。
## 棋盘与棋子怎么画
以格半径 s 为单位。复用 [renderer.js](../../../src/renderer.js) 的 classic 回退分支和 [tile-shape.js](../../../src/tile-shape.js),不要用 paper 的细线比例替换。
| 部件 | 当前实现 |
| --- | --- |
| 底格 | 正六角面,半径 s + 0.2;轻微重叠消除抗锯齿接缝 |
| 分格线 | 半径 s,线宽 max(1, s × 0.024) |
| 轨道外缘 | max(4, s × 0.16),颜色为当前 trackHalo |
| 轨道中心线 | max(2.5, s × 0.10),边界端点内收;中间轨道保持连接 |
| 棋子 | 半径 s × 0.985 的圆角六边形,rounding 由 roundedHex 实现 |
| 箭头 | 实心七点形状,尺度为棋子半径 × 0.58;不新增编号 |
前四个色库索引可使用 [tiles-matte-clean-v8.png](../../../assets/tiles-matte-clean-v8.png) 的原图中心采样,由 [sprites.js](../../../src/sprites.js) 提供裁切框。其余八色或图集未就绪时用纯色回退。保留共享箭头,不把方向烘焙进图集。
## 五种主题
| 主题 ID | 阶段 | 名称 | 氛围 |
| --- | --- | --- | --- |
| lagoon | 轻松 | 薄荷浅湾 | 浅湾背景,蓝青与日光黄 |
| apricot | 进阶 | 杏色午后 | 杏底配珊瑚、鸢尾蓝 |
| lavender | 烧脑 | 鸢尾花园 | 柔紫背景,绿与冰川青 |
| rose | 挑战 | 玫瑰暮色 | 玫瑰背景与明亮暖色 |
| midnight | 每周挑战 | 午夜极光 | 深靛蓝环境,棋盘仍是暖白 |
完整环境色、文字色、棋盘材质与 4/7/9/12 色有序 palette 见 tokens.json。源为 [design-tokens.js](../../../src/design-tokens.js) 的 LEVEL_THEMES / PIGMENTS。五主题共享基础棋盘材质;不能因为 midnight 是夜间环境就擅自把它的棋盘变深色。箭头按该色库的 3:1 对比度门槛选白色或深蓝灰。
## 控件与边界
底部道具保持圆形纯色底座:撤销蓝、重来紫、提示黄,均无阴影。顶部是紧凑的蓝灰导航控件。尺寸、通知位置、居中棋盘及金色奖励按 shared.md;角标只在设计示例出现。
修改此方案的基础语义色可能影响其他设计继承的面板、奖励与组件,需要检查全部五套。不要替换已接受图集,也不要把新增设计的颜料库复制到 PIGMENTS。
## 验收与接入
使用 `artDirection('calm')` → `directionTheme(direction, themeId, colorCount)` → `drawScene(..., {theme})`。详细代码和验证命令见 shared.md。
- 对比前四色的图集版本与纯色回退,确认方向一致且形状没有脏边。
- 检查移除、撤销、离场后颜色稳定;白/深箭头按对应色库切换。
- 检查五主题文字与轨道识别度,尤其深背景中的暖白棋盘。
- 现有 Cocos 示例可参考图集适配,但五套设计切换与当前动画尚未完成原生验收。
File diff suppressed because one or more lines are too long
+453
View File
@@ -0,0 +1,453 @@
{
"schemaVersion": 1,
"generated": true,
"generatedBy": "scripts/build-art-directions.mjs",
"pathBase": "repository-root",
"designId": "calm",
"artStyle": "classic",
"sources": {
"directions": "src/art-directions.js",
"baseTokens": "src/design-tokens.js",
"renderer": "src/renderer.js",
"tokenDrawing": "src/token-art.js",
"componentStyles": "design/art-directions.css",
"itemStyles": "design/game-tools.css"
},
"accent": "#6652B8",
"documentBackground": "#F7F6F2",
"sharedSemanticColors": {
"background": "#A8BECF",
"board": "#F2EEE5",
"boardGrid": "#89867D",
"track": "#435F78",
"trackHalo": "#F2EEE5",
"surface": "#F7F6F2",
"textPrimary": "#273B50",
"textSecondary": "#314A5F",
"primary": "#6652B8",
"onPrimary": "#FFFFFF",
"secondary": "#E4EAF0",
"utility": "#52697E",
"onUtility": "#FFFFFF",
"selection": "#DFD9F3",
"onSelection": "#3F327A",
"disabled": "#CBD4DD",
"onDisabled": "#526579",
"input": "#FFFFFF",
"reward": "#B9760C",
"warning": "#A44825",
"warningSurface": "#F9E3D2",
"scrim": "#273B507A",
"focus": "#3F327A",
"tileBlue": "#2392FD",
"tilePink": "#FC3E80",
"tileYellow": "#FDBC28",
"tilePurple": "#874FFD",
"arrowLight": "#FFFFFF",
"arrowDark": "#273B50"
},
"assets": {
"tileAtlas": "assets/tiles-matte-clean-v8.png",
"uiIconAtlas": "assets/ui-icons-generated.png"
},
"pigments": [
{
"index": 0,
"name": "晴空蓝",
"color": "#2392FD",
"arrow": "#FFFFFF"
},
{
"index": 1,
"name": "莓果粉",
"color": "#FC3E80",
"arrow": "#FFFFFF"
},
{
"index": 2,
"name": "日光黄",
"color": "#FDBC28",
"arrow": "#273B50"
},
{
"index": 3,
"name": "葡萄紫",
"color": "#874FFD",
"arrow": "#FFFFFF"
},
{
"index": 4,
"name": "湖水青",
"color": "#008B83",
"arrow": "#FFFFFF"
},
{
"index": 5,
"name": "珊瑚橙",
"color": "#EF704F",
"arrow": "#273B50"
},
{
"index": 6,
"name": "嫩芽绿",
"color": "#91C95B",
"arrow": "#273B50"
},
{
"index": 7,
"name": "鸢尾蓝",
"color": "#5267CF",
"arrow": "#FFFFFF"
},
{
"index": 8,
"name": "玫瑰红",
"color": "#C83F66",
"arrow": "#FFFFFF"
},
{
"index": 9,
"name": "蜜桃杏",
"color": "#F5AA83",
"arrow": "#273B50"
},
{
"index": 10,
"name": "冰川青",
"color": "#58C9D5",
"arrow": "#273B50"
},
{
"index": 11,
"name": "兰花紫",
"color": "#AE64C7",
"arrow": "#FFFFFF"
}
],
"themes": [
{
"id": "lagoon",
"label": "轻松",
"name": "薄荷浅湾",
"background": "#C5E4DF",
"ink": "#273B50",
"materials": {
"board": "#F2EEE5",
"boardGrid": "#89867D",
"track": "#435F78",
"trackHalo": "#F2EEE5"
},
"anchorPalette": [
0,
4,
2
],
"palettes": {
"4": [
0,
4,
2,
1
],
"7": [
0,
4,
2,
1,
3,
5,
6
],
"9": [
0,
4,
2,
1,
3,
5,
6,
7,
8
],
"12": [
0,
4,
2,
1,
3,
5,
6,
7,
8,
9,
10,
11
]
},
"contrast": {
"sceneText": 8.500875345312332,
"trackOnBoard": 5.763617196441413
}
},
{
"id": "apricot",
"label": "进阶",
"name": "杏色午后",
"background": "#F1D7B8",
"ink": "#273B50",
"materials": {
"board": "#F2EEE5",
"boardGrid": "#89867D",
"track": "#435F78",
"trackHalo": "#F2EEE5"
},
"anchorPalette": [
5,
7,
4,
9
],
"palettes": {
"4": [
5,
7,
4,
9
],
"7": [
5,
7,
4,
9,
0,
1,
2
],
"9": [
5,
7,
4,
9,
0,
1,
2,
3,
6
],
"12": [
5,
7,
4,
9,
0,
1,
2,
3,
6,
8,
10,
11
]
},
"contrast": {
"sceneText": 8.290501231652167,
"trackOnBoard": 5.763617196441413
}
},
{
"id": "lavender",
"label": "烧脑",
"name": "鸢尾花园",
"background": "#D8D0EB",
"ink": "#273B50",
"materials": {
"board": "#F2EEE5",
"boardGrid": "#89867D",
"track": "#435F78",
"trackHalo": "#F2EEE5"
},
"anchorPalette": [
3,
6,
8,
10
],
"palettes": {
"4": [
3,
6,
8,
10
],
"7": [
3,
6,
8,
10,
0,
1,
2
],
"9": [
3,
6,
8,
10,
0,
1,
2,
4,
5
],
"12": [
3,
6,
8,
10,
0,
1,
2,
4,
5,
7,
9,
11
]
},
"contrast": {
"sceneText": 7.737507893072143,
"trackOnBoard": 5.763617196441413
}
},
{
"id": "rose",
"label": "挑战",
"name": "玫瑰暮色",
"background": "#E7C3CE",
"ink": "#273B50",
"materials": {
"board": "#F2EEE5",
"boardGrid": "#89867D",
"track": "#435F78",
"trackHalo": "#F2EEE5"
},
"anchorPalette": [
8,
7,
2,
11
],
"palettes": {
"4": [
8,
7,
2,
11
],
"7": [
8,
7,
2,
11,
0,
1,
3
],
"9": [
8,
7,
2,
11,
0,
1,
3,
4,
5
],
"12": [
8,
7,
2,
11,
0,
1,
3,
4,
5,
6,
9,
10
]
},
"contrast": {
"sceneText": 7.164821183322833,
"trackOnBoard": 5.763617196441413
}
},
{
"id": "midnight",
"label": "每周挑战",
"name": "午夜极光",
"background": "#293956",
"ink": "#F7F6F2",
"materials": {
"board": "#F2EEE5",
"boardGrid": "#89867D",
"track": "#435F78",
"trackHalo": "#F2EEE5"
},
"anchorPalette": [
10,
11,
2,
1
],
"palettes": {
"4": [
10,
11,
2,
1
],
"7": [
10,
11,
2,
1,
0,
3,
4
],
"9": [
10,
11,
2,
1,
0,
3,
4,
5,
6
],
"12": [
10,
11,
2,
1,
0,
3,
4,
5,
6,
7,
8,
9
]
},
"contrast": {
"sceneText": 10.705759057123668,
"trackOnBoard": 5.763617196441413
}
}
]
}
+1
View File
@@ -0,0 +1 @@
<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta http-equiv="refresh" content="0;url=paper/index.html"><title>轻线纸境 · 设计稿已迁移</title></head><body><p>设计稿已整理到独立目录:<a href="paper/index.html">05 · 轻线纸境</a></p></body></html>
+5
View File
@@ -0,0 +1,5 @@
# 05 · 轻线纸境
本文件为旧入口。完整、人工维护的 Agent 规范已迁移到 [paper/design.md](paper/design.md)。
[设计稿](paper/index.html) · [配色数据](paper/tokens.json) · [Agent 接入说明](../README.md)
+57
View File
@@ -0,0 +1,57 @@
# 05 · 轻线纸境
设计 ID:`paper` · 渲染风格:`paper` · 英文名:PAPER ROUTES。最新新增的候选方案,可与其他方案同时保留。
[Agent 接入约定](../../shared.md) · [配色数据](tokens.json) · [完整设计稿](index.html) · [试玩与审核](../../../design-showcase.html?design=paper)
## 来源与设计目标
用户认可 [玩法变种实验页](../../../labs/variants/index.html) 的棋盘和棋子画法,要求整理成独立设计。提取暖白格面、浅蓝灰细轨道、陶橙/雾蓝纯色六角、细白边和线条箭头。五主题是这套画法的延展。
独立设计稿里的「双色试航」只保留初始布局作视觉对照;**不绘制棋盘外围彩色标记,不绘制棋子编号**。实验页的出口颜色匹配规则及外围色标不属于本设计,也不改变主游戏玩法。
## 棋盘与棋子怎么画
以格半径 s 为单位,复用 [renderer.js](../../../src/renderer.js) 的 paper 分支和 [token-art.js](../../../src/token-art.js) 的 paper 分支。
| 部件 | 当前实现 |
| --- | --- |
| 底格 | 正六角,半径 s × 0.97,保留细小格缝 |
| 分格线 | max(0.4, s × 0.025),使用主题 boardGrid |
| 轨道 | 单层细线 max(0.7, s × 0.055),不画粗 trackHalo、不做其他方案的端点内收 |
| 棋子 | 正六角纯色面,半径 s × 0.88,不能换成原版大圆角轮廓 |
| 白边 | #FFFFFFCC,线宽 s × 0.035 |
| 箭杆 | 局部坐标 (-0.30s, 0) → (0.30s, 0) |
| 箭头头部 | (0.08s, -0.17s) → (0.30s, 0) → (0.08s, 0.17s) |
| 箭头线条 | 白色、圆头圆接,线宽 s × 0.09;两段开放折线,不是实心三角 |
头部与箭杆一起按棋子方向或运动切线旋转。绘制分支内部 r = s × 0.88,再用 r / 0.88 还原单位,所以运动缩放时白边和箭头一起缩放。无棋子投影、高光、渐变;不用 PNG 冒充细线箭头。
## 五种主题
| 主题 ID | 阶段 | 名称 | 用色方向 |
| --- | --- | --- | --- |
| lagoon | 轻松 | 雾蓝信纸 | 实验页雾蓝环境、暖白格面、陶橙与雾蓝棋子 |
| apricot | 进阶 | 暖砂手札 | 暖砂纸底、灰棕细线 |
| lavender | 烧脑 | 鼠尾草页 | 浅灰绿纸底、蓝与苔绿棋子 |
| rose | 挑战 | 石墨练习 | 灰白图纸、石墨蓝细线 |
| midnight | 每周挑战 | 夜航图纸 | 深蓝灰板面、浅灰蓝轨道与暖色棋子 |
完整色值和每主题 4/7/9/12 色有序 palette 见 tokens.json,源为 [art-directions.js](../../../src/art-directions.js)。轻松主题关键参考色:环境 #EAF0F3、格面 #FAFBF7、分格 #D3DFE3、轨道 #B2C4CE、陶橙 #D66C4D、雾蓝 #477FC0。全部十二色固定白色箭头,其对比度至少 3:1。
浅色细轨道低于基础轨道的 3:1 目标,这是保留参考观感的已知取舍,不能为了套用原版指标而擅自加粗、加深或加轮廓。tokens 中的 contrast 是实测比值,不是合规认证。
## 控件与边界
顶部使用紧凑蓝灰控件,通知暖白、浅灰蓝边线,均无阴影。底部道具为圆形:撤销雾蓝、重来陶橙、提示赭金,1px 细白边,无阴影。旋转图标在浅主题为蓝灰,midnight 恢复白色;同时设置 body.dataset.design 和 body.dataset.theme。
通知位置、棋盘居中、大道具、金色发光星星以及示意角标都继承 shared.md。这里的“无阴影”不取消用户要求的奖励星星光感。
## 验收与接入
使用 `artDirection('paper')` 和 `directionTheme(direction, themeId, colorCount)`;禁止只换背景而继续使用 classic 的棋子和轨道画法。
- 对照实验页的细线与六角比例;参考布局没有外围色标与编号。
- 检查每个主题的长弯路,移动中仍为开放线条箭头、细白边棋子。
- 检查手机小图上的细线不断裂,以及午夜主题的旋转按钮颜色。
- 原生适配需检查圆头线端、透明细白边与格缝;尚未完成原生实机验收。
File diff suppressed because one or more lines are too long
+469
View File
@@ -0,0 +1,469 @@
{
"schemaVersion": 1,
"generated": true,
"generatedBy": "scripts/build-art-directions.mjs",
"pathBase": "repository-root",
"designId": "paper",
"artStyle": "paper",
"sources": {
"directions": "src/art-directions.js",
"baseTokens": "src/design-tokens.js",
"renderer": "src/renderer.js",
"tokenDrawing": "src/token-art.js",
"componentStyles": "design/art-directions.css",
"itemStyles": "design/game-tools.css"
},
"accent": "#294D60",
"documentBackground": "#F7F9F8",
"sharedSemanticColors": {
"background": "#A8BECF",
"board": "#F2EEE5",
"boardGrid": "#89867D",
"track": "#435F78",
"trackHalo": "#F2EEE5",
"surface": "#F7F6F2",
"textPrimary": "#273B50",
"textSecondary": "#314A5F",
"primary": "#6652B8",
"onPrimary": "#FFFFFF",
"secondary": "#E4EAF0",
"utility": "#52697E",
"onUtility": "#FFFFFF",
"selection": "#DFD9F3",
"onSelection": "#3F327A",
"disabled": "#CBD4DD",
"onDisabled": "#526579",
"input": "#FFFFFF",
"reward": "#B9760C",
"warning": "#A44825",
"warningSurface": "#F9E3D2",
"scrim": "#273B507A",
"focus": "#3F327A",
"tileBlue": "#2392FD",
"tilePink": "#FC3E80",
"tileYellow": "#FDBC28",
"tilePurple": "#874FFD",
"arrowLight": "#FFFFFF",
"arrowDark": "#273B50"
},
"assets": {
"tileAtlas": null,
"uiIconAtlas": "assets/ui-icons-generated.png"
},
"pigments": [
{
"index": 0,
"name": "陶橙",
"color": "#D66C4D",
"arrow": "#FFFFFF"
},
{
"index": 1,
"name": "雾蓝",
"color": "#477FC0",
"arrow": "#FFFFFF"
},
{
"index": 2,
"name": "鼠尾草",
"color": "#668877",
"arrow": "#FFFFFF"
},
{
"index": 3,
"name": "赭金",
"color": "#B58943",
"arrow": "#FFFFFF"
},
{
"index": 4,
"name": "雾紫",
"color": "#8B7297",
"arrow": "#FFFFFF"
},
{
"index": 5,
"name": "湖青",
"color": "#45868C",
"arrow": "#FFFFFF"
},
{
"index": 6,
"name": "陶红",
"color": "#B76260",
"arrow": "#FFFFFF"
},
{
"index": 7,
"name": "钢蓝",
"color": "#607B90",
"arrow": "#FFFFFF"
},
{
"index": 8,
"name": "暖木",
"color": "#B28168",
"arrow": "#FFFFFF"
},
{
"index": 9,
"name": "松绿",
"color": "#46765F",
"arrow": "#FFFFFF"
},
{
"index": 10,
"name": "烟蓝",
"color": "#6B8AAB",
"arrow": "#FFFFFF"
},
{
"index": 11,
"name": "藕灰",
"color": "#9C7284",
"arrow": "#FFFFFF"
}
],
"themes": [
{
"id": "lagoon",
"label": "轻松",
"name": "雾蓝信纸",
"background": "#EAF0F3",
"ink": "#12202A",
"materials": {
"board": "#FAFBF7",
"boardGrid": "#D3DFE3",
"track": "#B2C4CE",
"trackHalo": "#FAFBF7"
},
"anchorPalette": [
0,
1,
2,
3,
4,
5,
7
],
"palettes": {
"4": [
0,
1,
2,
3
],
"7": [
0,
1,
2,
3,
4,
5,
7
],
"9": [
0,
1,
2,
3,
4,
5,
7,
6,
8
],
"12": [
0,
1,
2,
3,
4,
5,
7,
6,
8,
9,
10,
11
]
},
"contrast": {
"sceneText": 14.423559313677755,
"trackOnBoard": 1.7299985145787213
}
},
{
"id": "apricot",
"label": "进阶",
"name": "暖砂手札",
"background": "#F3EEE5",
"ink": "#12202A",
"materials": {
"board": "#FFFCF5",
"boardGrid": "#E3DCD0",
"track": "#C4B8A4",
"trackHalo": "#FFFCF5"
},
"anchorPalette": [
0,
1,
3,
6,
8,
9,
4
],
"palettes": {
"4": [
0,
1,
3,
6
],
"7": [
0,
1,
3,
6,
8,
9,
4
],
"9": [
0,
1,
3,
6,
8,
9,
4,
2,
5
],
"12": [
0,
1,
3,
6,
8,
9,
4,
2,
5,
7,
10,
11
]
},
"contrast": {
"sceneText": 14.35675892235889,
"trackOnBoard": 1.90836044264981
}
},
{
"id": "lavender",
"label": "烧脑",
"name": "鼠尾草页",
"background": "#E8EFEB",
"ink": "#12202A",
"materials": {
"board": "#FAFCF8",
"boardGrid": "#D3DFD7",
"track": "#ADBCB2",
"trackHalo": "#FAFCF8"
},
"anchorPalette": [
1,
2,
3,
5,
9,
10,
0
],
"palettes": {
"4": [
1,
2,
3,
5
],
"7": [
1,
2,
3,
5,
9,
10,
0
],
"9": [
1,
2,
3,
5,
9,
10,
0,
4,
6
],
"12": [
1,
2,
3,
5,
9,
10,
0,
4,
6,
7,
8,
11
]
},
"contrast": {
"sceneText": 14.202869576847103,
"trackOnBoard": 1.916933130789821
}
},
{
"id": "rose",
"label": "挑战",
"name": "石墨练习",
"background": "#E7EBEE",
"ink": "#12202A",
"materials": {
"board": "#F8FAFB",
"boardGrid": "#D1DBE1",
"track": "#A5B6C1",
"trackHalo": "#F8FAFB"
},
"anchorPalette": [
0,
1,
4,
6,
7,
10,
11
],
"palettes": {
"4": [
0,
1,
4,
6
],
"7": [
0,
1,
4,
6,
7,
10,
11
],
"9": [
0,
1,
4,
6,
7,
10,
11,
2,
3
],
"12": [
0,
1,
4,
6,
7,
10,
11,
2,
3,
5,
8,
9
]
},
"contrast": {
"sceneText": 13.838133934955545,
"trackOnBoard": 1.9936733854132391
}
},
{
"id": "midnight",
"label": "每周挑战",
"name": "夜航图纸",
"background": "#253B4A",
"ink": "#FFFFFF",
"materials": {
"board": "#304A59",
"boardGrid": "#435E6C",
"track": "#849FAE",
"trackHalo": "#304A59"
},
"anchorPalette": [
0,
2,
3,
4,
5,
8,
10
],
"palettes": {
"4": [
0,
2,
3,
4
],
"7": [
0,
2,
3,
4,
5,
8,
10
],
"9": [
0,
2,
3,
4,
5,
8,
10,
1,
6
],
"12": [
0,
2,
3,
4,
5,
8,
10,
1,
6,
7,
9,
11
]
},
"contrast": {
"sceneText": 11.646406631180858,
"trackOnBoard": 3.357082701757936
}
}
]
}
File diff suppressed because one or more lines are too long
+3 -25
View File
@@ -1,27 +1,5 @@
# 积木轨道屋 # 03 · 积木轨道屋
圆润积木、原色塑胶与压下去有回弹的按钮。棋子像可以拿在手里的小零件。 本文件为旧入口。完整、人工维护的 Agent 规范已迁移到 [toy/design.md](toy/design.md)。
## 设计规则 [设计稿](toy/index.html) · [配色数据](toy/tokens.json) · [Agent 接入说明](../README.md)
- 同色深浅叠层,形成柔软厚度
- 红黄蓝绿积木配木色底板
- 圆角控件、凸起边缘与轻微高光
## 五种主题
| 难度 | 名称 | 环境色 |
| --- | --- | --- |
| 轻松 | 积木黄盒 | #F4C548 |
| 进阶 | 蓝色装配台 | #56AFE0 |
| 烧脑 | 橘红工坊 | #EA743E |
| 挑战 | 绿毯小城 | #439A73 |
| 每周挑战 | 太空玩具柜 | #244584 |
## 游戏道具与布局
- 撤销、重来、提示使用独立彩色底座,64–68px,图标 32px,标签 13px。顶部导航保持紧凑。道具使用纯色平面,不使用阴影、立体底边或渐变高光。
- 右上角预留次数或“▶ AD”角标。设计稿展示“撤销 3 次”和“广告获取提示”,仅为样式,不启用真实库存或广告逻辑。
- 棋盘在通知区与独立旋转区之间垂直居中。旋转区和道具区占据真实布局行,不覆盖棋盘。
此方案使用独立的棋子造型、轨道材质和控件样式,共用现有弯曲轨道玩法。审核页支持同时保留多套方案。
+50
View File
@@ -0,0 +1,50 @@
# 03 · 积木轨道屋
设计 ID:`toy` · 渲染风格:`toy` · 英文名:LITTLE PLAYWORKS。玩具质感候选方案,可与其他方案同时保留。
[Agent 接入约定](../../shared.md) · [配色数据](tokens.json) · [完整设计稿](index.html) · [试玩与审核](../../../design-showcase.html?design=toy)
## 设计目标
圆润积木、红黄蓝绿塑胶零件与原木色轨道板。厚度来自少量同色分层,棋子看起来可以拿在手里;不把整套方案做成灰雾或原版柔紫色系。
## 棋盘与棋子怎么画
复用 [renderer.js](../../../src/renderer.js) 的非 paper 棋盘、[token-art.js](../../../src/token-art.js) 的 toy 分支。r = 格半径 s × 0.985;tint 在 RGB 通道上按比例向白或黑混合。
| 部件 | 当前实现 |
| --- | --- |
| 轨道 | 分格 max(1, s × 0.024),外缘 max(4, s × 0.16),中心线 max(2.5, s × 0.10) |
| 下层厚度 | 半径 r × 0.94,向下偏移 r × 0.09,fill 暗化 30% |
| 中间层 | 半径 r × 0.94,fill 暗化 8% |
| 顶面 | 半径 r × 0.83,向上偏移 r × 0.06,原始 fill |
| 边缘高光 | 半径 r × 0.75 的局部边线,fill 提亮 50%,线宽 r × 0.045 |
| 中央压面 | 半径 r × 0.50 的圆,向上偏移 r × 0.04,fill 提亮 11% |
| 箭头 | 实心七点箭头,尺度 r × 0.58,保持清楚独立的方向 |
这套不依赖棋子图集,也不是物理材质或三维光照;所有效果由共享多边形、线与圆组成。移动和碰撞仍复用同一分层画法。
## 五种主题
| 主题 ID | 阶段 | 名称 | 独立用色方向 |
| --- | --- | --- | --- |
| lagoon | 轻松 | 积木黄盒 | 暖黄玩具盒、奶油板面、原色积木 |
| apricot | 进阶 | 蓝色装配台 | 亮蓝台面与原木色插板 |
| lavender | 烧脑 | 橘红工坊 | 橘红环境、木糖底板、绿蓝零件 |
| rose | 挑战 | 绿毯小城 | 绿垫与木色道路 |
| midnight | 每周挑战 | 太空玩具柜 | 太空蓝柜体、浅灰塑胶板 |
完整色值及每主题 4/7/9/12 色搭配见 tokens.json,源为 [art-directions.js](../../../src/art-directions.js)。配色要保留原色塑胶与暖木关系。箭头按 4.5:1 门槛选白或 #172D39,不能给所有黄色积木统一加白箭头。
## 控件与边界
现有顶部控件保留玩具边缘和圆润感。底部道具单独使用 24px 圆角、蓝/红/黄底色与奶油色 3px 边框;**没有**棋子的厚度、投影或高光。不要因名称里有“玩具”就重新给底部道具加阴影。尺寸、位置和未来角标按 shared.md。
## 验收与接入
使用 `artDirection('toy')` 和 `directionTheme(direction, themeId, colorCount)`,不要用 CSS 滤镜把 calm 染成玩具配色。
- 缩小到手机尺寸后,分层仍清楚且不遮住箭头。
- 蓝色和太空主题保持木色/浅灰板面,不能五套都用同一个奶油背景。
- 静态、运动与撤销造型一致;底部道具保持平面。
- 原生几何可复用绘制命令,原生控件和实机效果尚未验收。
File diff suppressed because one or more lines are too long
+469
View File
@@ -0,0 +1,469 @@
{
"schemaVersion": 1,
"generated": true,
"generatedBy": "scripts/build-art-directions.mjs",
"pathBase": "repository-root",
"designId": "toy",
"artStyle": "toy",
"sources": {
"directions": "src/art-directions.js",
"baseTokens": "src/design-tokens.js",
"renderer": "src/renderer.js",
"tokenDrawing": "src/token-art.js",
"componentStyles": "design/art-directions.css",
"itemStyles": "design/game-tools.css"
},
"accent": "#245EAF",
"documentBackground": "#FFFAEE",
"sharedSemanticColors": {
"background": "#A8BECF",
"board": "#F2EEE5",
"boardGrid": "#89867D",
"track": "#435F78",
"trackHalo": "#F2EEE5",
"surface": "#F7F6F2",
"textPrimary": "#273B50",
"textSecondary": "#314A5F",
"primary": "#6652B8",
"onPrimary": "#FFFFFF",
"secondary": "#E4EAF0",
"utility": "#52697E",
"onUtility": "#FFFFFF",
"selection": "#DFD9F3",
"onSelection": "#3F327A",
"disabled": "#CBD4DD",
"onDisabled": "#526579",
"input": "#FFFFFF",
"reward": "#B9760C",
"warning": "#A44825",
"warningSurface": "#F9E3D2",
"scrim": "#273B507A",
"focus": "#3F327A",
"tileBlue": "#2392FD",
"tilePink": "#FC3E80",
"tileYellow": "#FDBC28",
"tilePurple": "#874FFD",
"arrowLight": "#FFFFFF",
"arrowDark": "#273B50"
},
"assets": {
"tileAtlas": null,
"uiIconAtlas": "assets/ui-icons-generated.png"
},
"pigments": [
{
"index": 0,
"name": "积木红",
"color": "#DF4033",
"arrow": "#172D39"
},
{
"index": 1,
"name": "积木黄",
"color": "#F2C52E",
"arrow": "#172D39"
},
{
"index": 2,
"name": "积木蓝",
"color": "#2478C5",
"arrow": "#FFFFFF"
},
{
"index": 3,
"name": "积木绿",
"color": "#32945C",
"arrow": "#172D39"
},
{
"index": 4,
"name": "零件橙",
"color": "#EF862D",
"arrow": "#172D39"
},
{
"index": 5,
"name": "拼装紫",
"color": "#7652B2",
"arrow": "#FFFFFF"
},
{
"index": 6,
"name": "奶油白",
"color": "#EFE4C4",
"arrow": "#172D39"
},
{
"index": 7,
"name": "工具青",
"color": "#227B87",
"arrow": "#FFFFFF"
},
{
"index": 8,
"name": "砖红",
"color": "#B63845",
"arrow": "#FFFFFF"
},
{
"index": 9,
"name": "木棕",
"color": "#745B40",
"arrow": "#FFFFFF"
},
{
"index": 10,
"name": "玻璃蓝",
"color": "#73BFDC",
"arrow": "#172D39"
},
{
"index": 11,
"name": "草绿",
"color": "#A7BA56",
"arrow": "#172D39"
}
],
"themes": [
{
"id": "lagoon",
"label": "轻松",
"name": "积木黄盒",
"background": "#F4C548",
"ink": "#12202A",
"materials": {
"board": "#FFF7DE",
"boardGrid": "#B5A184",
"track": "#765238",
"trackHalo": "#FFF7DE"
},
"anchorPalette": [
0,
2,
3,
5,
4,
8,
10
],
"palettes": {
"4": [
0,
2,
3,
5
],
"7": [
0,
2,
3,
5,
4,
8,
10
],
"9": [
0,
2,
3,
5,
4,
8,
10,
1,
6
],
"12": [
0,
2,
3,
5,
4,
8,
10,
1,
6,
7,
9,
11
]
},
"contrast": {
"sceneText": 10.212613961554808,
"trackOnBoard": 6.463066023255321
}
},
{
"id": "apricot",
"label": "进阶",
"name": "蓝色装配台",
"background": "#56AFE0",
"ink": "#12202A",
"materials": {
"board": "#F5DFB6",
"boardGrid": "#B09C7C",
"track": "#386A8D",
"trackHalo": "#F5DFB6"
},
"anchorPalette": [
1,
0,
4,
3,
9,
6,
11
],
"palettes": {
"4": [
1,
0,
4,
3
],
"7": [
1,
0,
4,
3,
9,
6,
11
],
"9": [
1,
0,
4,
3,
9,
6,
11,
2,
5
],
"12": [
1,
0,
4,
3,
9,
6,
11,
2,
5,
7,
8,
10
]
},
"contrast": {
"sceneText": 6.797539779539623,
"trackOnBoard": 4.458036518249096
}
},
{
"id": "lavender",
"label": "烧脑",
"name": "橘红工坊",
"background": "#EA743E",
"ink": "#12202A",
"materials": {
"board": "#FFEFCB",
"boardGrid": "#BBA27B",
"track": "#695037",
"trackHalo": "#FFEFCB"
},
"anchorPalette": [
2,
3,
1,
7,
10,
5,
8
],
"palettes": {
"4": [
2,
3,
1,
7
],
"7": [
2,
3,
1,
7,
10,
5,
8
],
"9": [
2,
3,
1,
7,
10,
5,
8,
0,
4
],
"12": [
2,
3,
1,
7,
10,
5,
8,
0,
4,
6,
9,
11
]
},
"contrast": {
"sceneText": 5.582585700054348,
"trackOnBoard": 6.585478734470914
}
},
{
"id": "rose",
"label": "挑战",
"name": "绿毯小城",
"background": "#439A73",
"ink": "#12202A",
"materials": {
"board": "#F2D6A6",
"boardGrid": "#AC987A",
"track": "#476556",
"trackHalo": "#F2D6A6"
},
"anchorPalette": [
0,
1,
2,
4,
6,
9,
11
],
"palettes": {
"4": [
0,
1,
2,
4
],
"7": [
0,
1,
2,
4,
6,
9,
11
],
"9": [
0,
1,
2,
4,
6,
9,
11,
3,
5
],
"12": [
0,
1,
2,
4,
6,
9,
11,
3,
5,
7,
8,
10
]
},
"contrast": {
"sceneText": 4.82593081470767,
"trackOnBoard": 4.578974741098264
}
},
{
"id": "midnight",
"label": "每周挑战",
"name": "太空玩具柜",
"background": "#244584",
"ink": "#FFFFFF",
"materials": {
"board": "#D4E4E6",
"boardGrid": "#8DA4B2",
"track": "#35516F",
"trackHalo": "#D4E4E6"
},
"anchorPalette": [
1,
4,
0,
6,
10,
3,
8
],
"palettes": {
"4": [
1,
4,
0,
6
],
"7": [
1,
4,
0,
6,
10,
3,
8
],
"9": [
1,
4,
0,
6,
10,
3,
8,
2,
5
],
"12": [
1,
4,
0,
6,
10,
3,
8,
2,
5,
7,
9,
11
]
},
"contrast": {
"sceneText": 9.29429328953417,
"trackOnBoard": 6.2705655843638315
}
}
]
}
File diff suppressed because one or more lines are too long
+3 -25
View File
@@ -1,27 +1,5 @@
# 高彩游乐场 # 02 · 高彩游乐场
鲜明撞色、粗线条与贴纸式棋子。像一张能玩的彩色海报,明快、有力量。 本文件为旧入口。完整、人工维护的 Agent 规范已迁移到 [vivid/design.md](vivid/design.md)。
## 设计规则 [设计稿](vivid/index.html) · [配色数据](vivid/tokens.json) · [Agent 接入说明](../README.md)
- 高纯度主色,深色轮廓统一画面
- 轨道像印刷线,棋子像立起的贴纸
- 方圆按钮与错位硬阴影
## 五种主题
| 难度 | 名称 | 环境色 |
| --- | --- | --- |
| 轻松 | 青柠派对 | #B8F34A |
| 进阶 | 爆橙热浪 | #FF771B |
| 烧脑 | 钴蓝电场 | #3E42EE |
| 挑战 | 荧粉冲刺 | #FF3588 |
| 每周挑战 | 黑光嘉年华 | #1B104D |
## 游戏道具与布局
- 撤销、重来、提示使用独立彩色底座,64–68px,图标 32px,标签 13px。顶部导航保持紧凑。道具使用纯色平面,不使用阴影、立体底边或渐变高光。
- 右上角预留次数或“▶ AD”角标。设计稿展示“撤销 3 次”和“广告获取提示”,仅为样式,不启用真实库存或广告逻辑。
- 棋盘在通知区与独立旋转区之间垂直居中。旋转区和道具区占据真实布局行,不覆盖棋盘。
此方案使用独立的棋子造型、轨道材质和控件样式,共用现有弯曲轨道玩法。审核页支持同时保留多套方案。
+49
View File
@@ -0,0 +1,49 @@
# 02 · 高彩游乐场
设计 ID:`vivid` · 渲染风格:`vivid` · 英文名:CHROMA CLUB。高饱和候选方案,可与其他方案同时保留。
[Agent 接入约定](../../shared.md) · [配色数据](tokens.json) · [完整设计稿](index.html) · [试玩与审核](../../../design-showcase.html?design=vivid)
## 设计目标
鲜明撞色、深色轮廓与贴纸棋子,像可操作的彩色海报。用户明确要求五个主题都有高饱和感,不能只有轻松主题鲜艳,其余退回原版蓝紫粉背景。
## 棋盘与棋子怎么画
复用 [renderer.js](../../../src/renderer.js) 的非 paper 棋盘和 [token-art.js](../../../src/token-art.js) 的 vivid 分支。以格半径 s、棋子基准半径 r = s × 0.985 为单位。
| 部件 | 当前实现 |
| --- | --- |
| 分格/轨道 | 分格 max(1, s × 0.024),轨道外缘 max(4, s × 0.16),中心线 max(2.5, s × 0.10) |
| 棋子正面 | 半径 r × 0.94 的圆角六边形,独立颜料色纯色填充 |
| 硬边底层 | 同形向右 r × 0.04、向下 r × 0.07 偏移,颜色 #30264C |
| 深色描边 | #30264C,线宽 r × 0.065 |
| 白色短划 | 左上方短线,线宽 r × 0.055;用于贴纸感 |
| 箭头 | 实心七点箭头,尺度 r × 0.58,颜色取本套 pigments.arrow |
棋子全部由多边形和线绘制,不使用 calm 的棋子 PNG。轨道几何与主玩法一致,颜色取该主题展开后的 materials,不能把装饰短线当作新路径。
## 五种主题
| 主题 ID | 阶段 | 名称 | 独立用色方向 |
| --- | --- | --- | --- |
| lagoon | 轻松 | 青柠派对 | 青柠大背景与奶油棋盘 |
| apricot | 进阶 | 爆橙热浪 | 亮橙环境、明黄棋盘与冷色棋子 |
| lavender | 烧脑 | 钴蓝电场 | 纯钴蓝环境、荧光青柠棋盘 |
| rose | 挑战 | 荧粉冲刺 | 荧光粉舞台与明黄、蓝绿 |
| midnight | 每周挑战 | 黑光嘉年华 | 深紫黑底、荧光绿轨道 |
完整色值和每个主题的 4/7/9/12 色有序 palette 见 tokens.json;源为 [art-directions.js](../../../src/art-directions.js)。12 色独立于原版。主题背景保留较高 HSL 饱和度;当前测试要求至少 0.65。箭头按 4.5:1 门槛在白与 #172D39 中选取。
## 控件与边界
顶部控件与通知沿用深轮廓和贴纸风格。底部道具服从共用的扁平要求:撤销电青、重来亮粉、提示明黄,3px 深色边线,不加棋子的偏移硬阴影。顶部风格不能覆盖 game-tools.css 的最后加载规则。
## 验收与接入
使用 `artDirection('vivid')` 和 `directionTheme(direction, themeId, colorCount)`;完整流程见 shared.md。
- 五主题都应与原版明显不同,保持高纯度用色,棋子与底盘容易分离。
- 黑光主题检查浅色轨道和深色板面,其他主题检查深线与亮板面的关系。
- 静态、移动、碰撞时都使用同一贴纸轮廓和箭头。
- 底部道具无阴影;原生端尚未接入并实机验收此套风格。
File diff suppressed because one or more lines are too long
+469
View File
@@ -0,0 +1,469 @@
{
"schemaVersion": 1,
"generated": true,
"generatedBy": "scripts/build-art-directions.mjs",
"pathBase": "repository-root",
"designId": "vivid",
"artStyle": "vivid",
"sources": {
"directions": "src/art-directions.js",
"baseTokens": "src/design-tokens.js",
"renderer": "src/renderer.js",
"tokenDrawing": "src/token-art.js",
"componentStyles": "design/art-directions.css",
"itemStyles": "design/game-tools.css"
},
"accent": "#4A24E8",
"documentBackground": "#FFFCF0",
"sharedSemanticColors": {
"background": "#A8BECF",
"board": "#F2EEE5",
"boardGrid": "#89867D",
"track": "#435F78",
"trackHalo": "#F2EEE5",
"surface": "#F7F6F2",
"textPrimary": "#273B50",
"textSecondary": "#314A5F",
"primary": "#6652B8",
"onPrimary": "#FFFFFF",
"secondary": "#E4EAF0",
"utility": "#52697E",
"onUtility": "#FFFFFF",
"selection": "#DFD9F3",
"onSelection": "#3F327A",
"disabled": "#CBD4DD",
"onDisabled": "#526579",
"input": "#FFFFFF",
"reward": "#B9760C",
"warning": "#A44825",
"warningSurface": "#F9E3D2",
"scrim": "#273B507A",
"focus": "#3F327A",
"tileBlue": "#2392FD",
"tilePink": "#FC3E80",
"tileYellow": "#FDBC28",
"tilePurple": "#874FFD",
"arrowLight": "#FFFFFF",
"arrowDark": "#273B50"
},
"assets": {
"tileAtlas": null,
"uiIconAtlas": "assets/ui-icons-generated.png"
},
"pigments": [
{
"index": 0,
"name": "电蓝",
"color": "#2455E8",
"arrow": "#FFFFFF"
},
{
"index": 1,
"name": "热粉",
"color": "#E52879",
"arrow": "#172D39"
},
{
"index": 2,
"name": "柠檬黄",
"color": "#FFE13E",
"arrow": "#172D39"
},
{
"index": 3,
"name": "电紫",
"color": "#7136D8",
"arrow": "#FFFFFF"
},
{
"index": 4,
"name": "深青",
"color": "#007C70",
"arrow": "#FFFFFF"
},
{
"index": 5,
"name": "火橙",
"color": "#FF622C",
"arrow": "#172D39"
},
{
"index": 6,
"name": "荧光绿",
"color": "#BBE929",
"arrow": "#172D39"
},
{
"index": 7,
"name": "钴蓝",
"color": "#074DFF",
"arrow": "#FFFFFF"
},
{
"index": 8,
"name": "警示红",
"color": "#EB174E",
"arrow": "#172D39"
},
{
"index": 9,
"name": "亮橘",
"color": "#FF9500",
"arrow": "#172D39"
},
{
"index": 10,
"name": "电光青",
"color": "#08E8E1",
"arrow": "#172D39"
},
{
"index": 11,
"name": "激光紫",
"color": "#A92BFF",
"arrow": "#FFFFFF"
}
],
"themes": [
{
"id": "lagoon",
"label": "轻松",
"name": "青柠派对",
"background": "#B8F34A",
"ink": "#12202A",
"materials": {
"board": "#FFFBE7",
"boardGrid": "#AB9C82",
"track": "#30264C",
"trackHalo": "#FFFBE7"
},
"anchorPalette": [
0,
4,
2,
1,
3,
5,
6
],
"palettes": {
"4": [
0,
4,
2,
1
],
"7": [
0,
4,
2,
1,
3,
5,
6
],
"9": [
0,
4,
2,
1,
3,
5,
6,
7,
8
],
"12": [
0,
4,
2,
1,
3,
5,
6,
7,
8,
9,
10,
11
]
},
"contrast": {
"sceneText": 12.60682592893418,
"trackOnBoard": 13.40477966198369
}
},
{
"id": "apricot",
"label": "进阶",
"name": "爆橙热浪",
"background": "#FF771B",
"ink": "#12202A",
"materials": {
"board": "#FFEF45",
"boardGrid": "#B78335",
"track": "#462654",
"trackHalo": "#FFEF45"
},
"anchorPalette": [
7,
3,
10,
8,
2,
0,
11
],
"palettes": {
"4": [
7,
3,
10,
8
],
"7": [
7,
3,
10,
8,
2,
0,
11
],
"9": [
7,
3,
10,
8,
2,
0,
11,
1,
4
],
"12": [
7,
3,
10,
8,
2,
0,
11,
1,
4,
5,
6,
9
]
},
"contrast": {
"sceneText": 6.246500288112486,
"trackOnBoard": 10.616992375930863
}
},
{
"id": "lavender",
"label": "烧脑",
"name": "钴蓝电场",
"background": "#3E42EE",
"ink": "#FFFFFF",
"materials": {
"board": "#C9F849",
"boardGrid": "#789344",
"track": "#252269",
"trackHalo": "#C9F849"
},
"anchorPalette": [
2,
9,
1,
10,
6,
5,
8
],
"palettes": {
"4": [
2,
9,
1,
10
],
"7": [
2,
9,
1,
10,
6,
5,
8
],
"9": [
2,
9,
1,
10,
6,
5,
8,
0,
3
],
"12": [
2,
9,
1,
10,
6,
5,
8,
0,
3,
4,
7,
11
]
},
"contrast": {
"sceneText": 6.524329011511401,
"trackOnBoard": 11.251830170037788
}
},
{
"id": "rose",
"label": "挑战",
"name": "荧粉冲刺",
"background": "#FF3588",
"ink": "#12202A",
"materials": {
"board": "#FFF4CE",
"boardGrid": "#C491A1",
"track": "#502369",
"trackHalo": "#FFF4CE"
},
"anchorPalette": [
0,
6,
10,
2,
7,
4,
9
],
"palettes": {
"4": [
0,
6,
10,
2
],
"7": [
0,
6,
10,
2,
7,
4,
9
],
"9": [
0,
6,
10,
2,
7,
4,
9,
1,
3
],
"12": [
0,
6,
10,
2,
7,
4,
9,
1,
3,
5,
8,
11
]
},
"contrast": {
"sceneText": 4.83248512643413,
"trackOnBoard": 10.688104062178175
}
},
{
"id": "midnight",
"label": "每周挑战",
"name": "黑光嘉年华",
"background": "#1B104D",
"ink": "#FFFFFF",
"materials": {
"board": "#35225D",
"boardGrid": "#837B9F",
"track": "#D9FF55",
"trackHalo": "#35225D"
},
"anchorPalette": [
10,
2,
6,
1,
9,
11,
5
],
"palettes": {
"4": [
10,
2,
6,
1
],
"7": [
10,
2,
6,
1,
9,
11,
5
],
"9": [
10,
2,
6,
1,
9,
11,
5,
0,
3
],
"12": [
10,
2,
6,
1,
9,
11,
5,
0,
3,
4,
7,
8
]
},
"contrast": {
"sceneText": 17.102604907549345,
"trackOnBoard": 11.952226494271752
}
}
]
}
+30
View File
@@ -0,0 +1,30 @@
// Run from the repository root: node design/examples/render-design.mjs paper lagoon 7
// This engine-independent example records geometry; it does not validate PNG pixels or UI.
import { pathToFileURL } from 'node:url';
import { ART_DIRECTIONS, directionTheme } from '../../src/art-directions.js';
import { drawScene } from '../../src/renderer.js';
import { layout } from '../../src/geometry.js';
import { createStudyLevel } from '../../src/study-levels.js';
const level = createStudyLevel(3);
export function recordDesign(designId = 'paper', themeId = 'lagoon', colorCount = 7) {
const direction = ART_DIRECTIONS.find(d => d.id === designId);
if (!direction) throw new Error(`Unknown design: ${designId}`);
if (!direction.themes.some(t => t.id === themeId)) throw new Error(`Unknown theme: ${themeId}`);
if (![4, 7, 9, 12].includes(colorCount)) throw new Error(`Unsupported color count: ${colorCount}`);
const theme = directionTheme(direction, themeId, colorCount), commands = [];
const painter = {
polygon: (points, fill) => commands.push({ type: 'polygon', points, fill }),
line: (points, color, width) => commands.push({ type: 'line', points, color, width }),
circle: (x, y, radius, fill) => commands.push({ type: 'circle', x, y, radius, fill }),
};
drawScene(painter, level, level.tiles, layout(level.cells, 600, 600), { theme });
return { designId, themeId, colorCount, background: theme.background, palette: theme.palette, cellCount: level.cells.length, commands };
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
const [designId = 'paper', themeId = 'lagoon', count = '7'] = process.argv.slice(2);
const { commands, ...summary } = recordDesign(designId, themeId, Number(count));
console.log(JSON.stringify({ ...summary, commandCount: commands.length }, null, 2));
}
+19
View File
File diff suppressed because one or more lines are too long
+97
View File
@@ -0,0 +1,97 @@
{
"schemaVersion": 1,
"generated": true,
"generatedBy": "scripts/build-art-directions.mjs",
"pathBase": "repository-root",
"entry": "design/README.md",
"agentGuide": "design/AGENTS.md",
"sharedRules": "design/shared.md",
"catalog": "design/index.html",
"example": "design/examples/render-design.mjs",
"defaultDesignId": "calm",
"latestDesignId": "paper",
"selectionPolicy": "multiple-candidates",
"themeIds": [
"lagoon",
"apricot",
"lavender",
"rose",
"midnight"
],
"paletteCounts": [
4,
7,
9,
12
],
"sources": {
"directions": "src/art-directions.js",
"baseTokens": "src/design-tokens.js",
"renderer": "src/renderer.js",
"tokenDrawing": "src/token-art.js",
"componentStyles": "design/art-directions.css",
"itemStyles": "design/game-tools.css"
},
"designs": [
{
"id": "calm",
"code": "01",
"name": "柔和原色",
"artStyle": "classic",
"status": "existing-default",
"directory": "design/directions/calm",
"designDoc": "design/directions/calm/design.md",
"tokens": "design/directions/calm/tokens.json",
"preview": "design/directions/calm/index.html",
"legacyPreview": "design/directions/calm.html"
},
{
"id": "vivid",
"code": "02",
"name": "高彩游乐场",
"artStyle": "vivid",
"status": "review-candidate",
"directory": "design/directions/vivid",
"designDoc": "design/directions/vivid/design.md",
"tokens": "design/directions/vivid/tokens.json",
"preview": "design/directions/vivid/index.html",
"legacyPreview": "design/directions/vivid.html"
},
{
"id": "toy",
"code": "03",
"name": "积木轨道屋",
"artStyle": "toy",
"status": "review-candidate",
"directory": "design/directions/toy",
"designDoc": "design/directions/toy/design.md",
"tokens": "design/directions/toy/tokens.json",
"preview": "design/directions/toy/index.html",
"legacyPreview": "design/directions/toy.html"
},
{
"id": "animals",
"code": "04",
"name": "动物弯弯镇",
"artStyle": "animals",
"status": "review-candidate",
"directory": "design/directions/animals",
"designDoc": "design/directions/animals/design.md",
"tokens": "design/directions/animals/tokens.json",
"preview": "design/directions/animals/index.html",
"legacyPreview": "design/directions/animals.html"
},
{
"id": "paper",
"code": "05",
"name": "轻线纸境",
"artStyle": "paper",
"status": "review-candidate",
"directory": "design/directions/paper",
"designDoc": "design/directions/paper/design.md",
"tokens": "design/directions/paper/tokens.json",
"preview": "design/directions/paper/index.html",
"legacyPreview": "design/directions/paper.html"
}
]
}
+116
View File
@@ -0,0 +1,116 @@
# 五套设计的接入与维护约定
适用于主游戏的 01–05 五套设计。先读 [方案目录](README.md),确定设计 ID,再读对应的 `design.md`。本文和各套 design.md 是人工维护的设计规则;数值以实现源及其生成的 tokens.json 为准。发现文档与实现不一致时核对用户已确认要求并同步修正,不能用旧说明撤回新要求。
## 选择一个设计与主题
三个参数分别控制不同的事:
| 参数 | 合法值 | 作用 |
| --- | --- | --- |
| `design` | `calm` / `vivid` / `toy` / `animals` / `paper` | 棋子造型、轨道画法、配色库和控件风格 |
| `theme` | `lagoon` / `apricot` / `lavender` / `rose` / `midnight` | 轻松、进阶、烧脑、挑战、每周挑战的视觉主题 |
| `colors` | 4 / 7 / 9 / 12 | 主游戏单关使用的棋子配色数量,默认 7 |
在根目录执行 `npm run dev`。打开 [paper 的轻松主题](../index.html?design=paper&theme=lagoon&colors=7),或 [paper 的夜间审核](../design-showcase.html?design=paper&theme=midnight#phone)。审核页用控件切换色数;`colors` URL 参数由主游戏读取。设计总览的主题切换只用于视觉对照。
不传 design 时使用 calm。主游戏不传 theme 时按关卡评分选前四种主题;midnight 需显式指定。主题名与配色不改变难度或玩法。编辑模式和工坊试玩使用基础自由配色,不能拿它们判断设计参数是否生效。审核记录保存在 `hex-trails-art-review-v1`,与游戏存档分开。
## Agent 如何读取数据
1. 读取 [manifest.json](manifest.json),确认 `schemaVersion === 1`。
2. 按 `designs[].id` 找方案,读取 `designDoc` 与 `tokens`。manifest 的所有路径字段均从仓库根目录解析,不从当前 JSON 目录拼接。
3. tokens 中的 `themes` 给出环境、文字和已展开的棋盘材质。`palettes["7"]` 是 7 色时的**有序色库索引**;索引指向本文件的 `pigments`,不能借用另一套设计的色库。
4. tokens 是当前实现的只读数值导出,不能单独描述完整造型或所有 CSS 控件。几何和组件规则仍需读取 design.md、共享渲染器及 CSS。`design/tokens.json` 是旧有基础语义 token 导出,不包含五套方案全集。
manifest 不存用户审核意见,也不表示最新方案已被选为默认。文档中的“最新”只表示新增顺序。
## 复用现有渲染器
浏览器页面已有 `<canvas id="board" width="600" height="600"></canvas>` 时,可在仓库根目录页面的 ES module 中运行以下代码;通过 HTTP 加载,不直接双击文件:
```js
import { artDirection, directionTheme } from './src/art-directions.js';
import { CanvasPainter, drawScene } from './src/renderer.js';
import { layout } from './src/geometry.js';
import { createStudyLevel } from './src/study-levels.js';
import { TILE_ATLAS } from './src/sprites.js';
const direction = artDirection('paper');
const theme = directionTheme(direction, 'lagoon', 7);
const level = createStudyLevel(3);
const canvas = document.querySelector('#board');
const painter = new CanvasPainter(canvas.getContext('2d'));
const atlas = new Image();
atlas.src = TILE_ATLAS;
await atlas.decode();
painter.atlas = atlas;
canvas.style.background = theme.background;
canvas.style.color = theme.ink;
painter.ctx.clearRect(0, 0, canvas.width, canvas.height);
drawScene(painter, level, level.tiles,
layout(level.cells, canvas.width, canvas.height), { theme });
```
`drawScene` 只画棋盘、轨道与棋子,不画画布背景,也不自动清屏。响应式画布使用实际显示尺寸和 DPR,复用 [board-viewport.js](../src/board-viewport.js) / app.js 的适配逻辑。不要根据每枚棋子的位置另设显示比例。
整盘绘制时传入完整的 `level.tiles` 与当前剩余 tiles。`drawScene` 以棋子在初始 `level.tiles` 中的索引映射主题 palette,因此移除、撤销与运动不换色;不要按“剩余棋子第几个”重新编号。独立棋子样本才使用 `appearanceTile({color: pigmentIndex, dir}, theme)` 再调用 `drawTile`,这里的 color 已是该设计的色库索引。
所有方向以弧度 `-Math.PI / 2 + dir * Math.PI / 3` 绘制,dir 为 0–5。移动中的棋子经共享 `drawExit` / `drawTile` 保持同一造型。不要复制静态棋子绘制代码实现另一套移动画法。
## 接入完整游戏界面
| 接入点 | 实际来源 | 注意事项 |
| --- | --- | --- |
| 设计与主题定义 | [src/art-directions.js](../src/art-directions.js) | ID、12 色库、五主题、artStyle;calm 复用基础色库 |
| 基础语义色、字体、动效 | [src/design-tokens.js](../src/design-tokens.js) | 被各设计继承;不能用基础色覆盖新方案的独立色库 |
| 场景几何与运动绘制 | [src/renderer.js](../src/renderer.js)、[src/token-art.js](../src/token-art.js) | 使用同一套命令和曲线路径 |
| 主游戏选择逻辑 | [src/app.js](../src/app.js) | 设置 body.dataset.design / theme,应用场景背景与文字色 |
| 控件风格 | [art-directions.css](art-directions.css) | 用 body[data-design] 限定,避免影响其他方案 |
| 底部道具及布局 | [game-tools.css](game-tools.css) | 最后加载,保证扁平道具覆盖既有立体风格 |
| 通知与通用组件 | [notifications.css](notifications.css)、[components.css](components.css) | 通知位于剩余步数下方,奖励为金色发光星星 |
| 图集与裁切 | [src/sprites.js](../src/sprites.js)、[src/ui-skin.js](../src/ui-skin.js) | 棋子图集只在 calm 分支使用;UI 图标各套共享 |
主游戏 CSS 加载顺序参考 [index.html](../index.html):style.css → tokens.css → components.css → notifications.css → campaign.css → art-directions.css → game-tools.css。只有设置 CSS 主题而没给 drawScene 传 theme,棋盘仍会使用基础画法。只有给画布传 theme 而没设置 body.dataset.design,控件不会切换。
## 所有方案共用的产品要求
- 棋盘在通知区与旋转区之间垂直居中。旋转与道具占真实布局行,不绝对定位压住棋盘。适配手机、Pad、折叠屏和桌面。
- 撤销、重来、提示是游戏道具,与顶部紧凑导航分开。常规底座 64px,宽度至少 600px 时为 68px,短屏高度不超过 600px 时为 56px;图标 32px、标签 13px。底座无阴影、立体底边或渐变高光,方案的棋子材质不覆盖这条要求。
- 次数和“▶ AD”角标只用于设计示例,当前不接入真实库存、广告或付费获取逻辑。
- 奖励星星为黄色/金色并带光感。通知固定在剩余步数下方,不能挪回棋盘下方。
- 路线以弯曲路径为主要观看内容。动画复用 [exit-animation.js](../src/exit-animation.js) 和 [collision-animation.js](../src/collision-animation.js)。离场时长按实际路径长度计算,不把旧文档中的固定时长或上下限当作当前速度公式。
- 设计切换不改规则、关卡布局、编号、关卡版本或存档。设计编号 01–05 与关卡 v0.1–v0.5 是两套不同的标识。
## 修改与验证
只改配色:编辑 art-directions.js 中指定设计;calm 的色库及基础主题在 design-tokens.js。改造型:编辑 token-art.js 的指定分支,必要时改 renderer.js 的相关绘制,并同步该套 design.md。改控件:编辑对应 CSS 的作用域规则;共用道具约束仍需保留。
文档整理不需要重新生成关卡。根目录执行:
```sh
npm run design
node --test tests/design-packages.test.js tests/art-directions.test.js tests/themes.test.js
node design/examples/render-design.mjs paper lagoon 7
node scripts/build.mjs
```
`npm run design` 会刷新 manifest、每套 tokens、HTML 和旧入口;**不覆盖人工维护的 design.md、shared.md、README.md**。文档测试检查目录与链接、数值导出与运行时一致性,并执行接入示例。视觉修改另检查五主题、长弯路离场、碰撞、旋转与撤销;至少查看 390×844 手机、短屏、平板和桌面。纯文档修改检查链接和预览能加载即可。原生验证单独报告。
## 跨引擎范围与已知限制
绘制抽象只要求 polygon、line、circle,calm 可额外使用 sprite 图集。路径单位是格半径;不能把示例画布尺寸当作固定像素规范。实际应用背景和 UI 布局由宿主引擎实现。
[cocos/HexTrailsBoard.ts](../cocos/HexTrailsBoard.ts) 是适配示例,当前没有传入这五套设计的 theme,且动画时序仍有旧值;[cocos/CandyUiSkin.ts](../cocos/CandyUiSkin.ts) 也不是五套设计的完整原生控件实现。移植时需显式接入主题、核对圆头线条与图层,并按浏览器参考画面验收;本目录不代表原生移植已完成。
paper 的浅色轨道是用户认可的特例,轨道与棋盘对比度低于基础 3:1 目标;不要为通过基础轨道指标自行加深。文字和方向需保持可读。各套 design.md 标明具体差异。
## 实现依据与旧文档
| 依据 | 确认的事实 | 修改/复现入口 |
| --- | --- | --- |
| art-directions.js 导出的五个 ID | 五套方案共存且都有五种主题 | manifest.json、每套 tokens.json |
| renderer.js 的 visualTile 与 paper 分支 | 固定初始索引配色,paper 使用更细轨道 | render-design.mjs、art-directions.test.js |
| game-tools.css 的最后加载覆盖 | 道具保持扁平且分行布局 | 各套 index.html、主游戏 |
[根目录 Design.md](../Design.md) / Design.html 保留基础组件规范与历史说明,[docs/art-direction.md](../docs/art-direction.md) 保留较早的制作简报。它们的“唯一色源”、轨道对比度和动画旧值不能无差别套用到五套设计。新增方案的接入从本目录开始。
+2
View File
@@ -1,5 +1,7 @@
# HEX TRAILS 美术方向与制作简报 # HEX TRAILS 美术方向与制作简报
> 历史简报,保留 2026-09-11 的核查背景。当前五套主游戏设计的 Agent 入口已整理到 [design/README.md](../design/README.md),规则见 [shared.md](../design/shared.md) 与各套 design.md,数值见对应 tokens.json。下文“唯一数值来源”、数量、时序和设计债描述可能属于旧快照,不应覆盖当前五套方案的独立规则。
版本:2026-09-11。本文面向承担本项目**美术设计**工作的会话或成员,说明角色边界、设计系统事实、改色与素材流程、已知设计债,以及三条工作线的入手点。 版本:2026-09-11。本文面向承担本项目**美术设计**工作的会话或成员,说明角色边界、设计系统事实、改色与素材流程、已知设计债,以及三条工作线的入手点。
本文是**工作简报**,不是设计规范本身。数值与规则的唯一来源仍是 `src/design-tokens.js` 与 [Design.md](../Design.md);本文负责把"怎么改、别踩什么、先做什么"讲清楚。 本文是**工作简报**,不是设计规范本身。数值与规则的唯一来源仍是 `src/design-tokens.js` 与 [Design.md](../Design.md);本文负责把"怎么改、别踩什么、先做什么"讲清楚。
+20 -6
View File
@@ -1,11 +1,25 @@
import { mkdir, writeFile } from 'node:fs/promises'; import { mkdir, readFile, writeFile } from 'node:fs/promises';
import { itemStudy, itemIcon } from '../src/item-study.js'; import { itemStudy, itemIcon } from '../src/item-study.js';
import { ICON_ATLAS } from '../src/ui-skin.js'; import { ICON_ATLAS } from '../src/ui-skin.js';
import { ART_DIRECTIONS } from '../src/art-directions.js'; import { ART_DIRECTIONS } from '../src/art-directions.js';
import { buildDesignCatalog } from './design-catalog.mjs';
import { buildDesignPackages } from './design-packages.mjs';
const out=new URL('../design/directions/',import.meta.url);await mkdir(out,{recursive:true}); const out=new URL('../design/directions/',import.meta.url);await mkdir(out,{recursive:true});
for(const d of ART_DIRECTIONS){ const referenceLevel=JSON.parse(await readFile(new URL('../labs/variants/levels.json',import.meta.url),'utf8')).find(l=>l.id==='color-1');
const html=`<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${d.name} · HEX TRAILS 设计稿</title><link rel="stylesheet" href="../showcase.css"><link rel="stylesheet" href="../art-directions.css"><link rel="stylesheet" href="../game-tools.css"></head><body class="sheet-page" data-design="${d.id}" style="--paper:${d.paper};--accent:${d.accent};--bg:${d.themes[0].background}"><main class="sheet-main"><nav class="sheet-nav"><a href="../../design-showcase.html?design=${d.id}">← 回到设计审核</a><span>HEX TRAILS / ART DIRECTION ${d.code}</span></nav><header class="sheet-hero"><div><span class="eyebrow">${d.en}</span><h1>${d.name}</h1><p>${d.description}</p></div><canvas id="sheet-tokens" class="sheet-token-row" width="720" height="270" aria-label="${d.name}棋子造型设计"></canvas></header><section><div class="section-heading"><div><span class="eyebrow">01 / FIVE WORLDS</span><h2>同一风格,五种主题</h2></div><p>统一的弯曲轨道关卡 · 用实际渲染检查识别度</p></div><div class="sheet-themes">${d.themes.map(t=>`<article class="sheet-theme" style="--bg:${t.background};--ink:${t.ink}"><small>${t.label}</small><h3>${t.name}</h3><canvas data-sheet-theme="${t.id}" width="480" height="520" aria-label="${t.label}主题的弯曲棋盘"></canvas><div class="sheet-swatches">${t.palette.map(i=>`<i style="background:${d.pigments[i].color}" title="${d.pigments[i].color}"></i>`).join('')}</div><small>${t.background}</small></article>`).join('')}</div></section><section class="sheet-components"><div class="sheet-panel"><span class="eyebrow">02 / DESIGN LANGUAGE</span><h2>造型与交互规则</h2><ul>${d.principles.map(p=>`<li>${p}</li>`).join('')}<li>所有棋子保留独立方向箭头,颜色和表情不改变规则。</li><li>离场与碰撞共用游戏动画,切换设计保持路线与时长一致。</li></ul><p class="sheet-caption">这是一套可与其他方案同时保留的候选设计。到审核页勾选保留、记录修改意见;不会替换你的其他选择。</p><a class="sheet-link" href="../../design-showcase.html?design=${d.id}">审核与体验这套设计 ↗</a></div><div class="sheet-panel"><span class="eyebrow">03 / COMPONENT STUDY</span><h2>控件与奖励</h2><div class="component-preview">${itemStudy(name=>itemIcon(name,'../../'+ICON_ATLAS))}<div class="sample-modal"><div class="stars" aria-label="三颗金色星星">★ ★ ★</div><h3>漂亮,全部过关!</h3><p>每一次转弯,都找到出口。</p><a class="sheet-link" href="../../design-showcase.html?design=${d.id}">继续旅程 →</a></div></div><p class="sheet-caption">底部道具独立于顶部导航。棋盘在通知与旋转区之间垂直居中;可操作棋盘在设计审核页。</p></div></section></main><script type="module" src="../../src/direction-sheet.js"></script></body></html>`; function referenceStudy(d){
await writeFile(new URL(`${d.id}.html`,out),html); if(!d.source)return '';
await writeFile(new URL(`${d.id}.md`,out),`# ${d.name}\n\n${d.description}\n\n## 设计规则\n\n${d.principles.map(p=>`- ${p}`).join('\n')}\n\n## 五种主题\n\n| 难度 | 名称 | 环境色 |\n| --- | --- | --- |\n${d.themes.map(t=>`| ${t.label} | ${t.name} | ${t.background} |`).join('\n')}\n\n## 游戏道具与布局\n\n- 撤销、重来、提示使用独立彩色底座,64–68px,图标 32px,标签 13px。顶部导航保持紧凑。道具使用纯色平面,不使用阴影、立体底边或渐变高光。\n- 右上角预留次数或“▶ AD”角标。设计稿展示“撤销 3 次”和“广告获取提示”,仅为样式,不启用真实库存或广告逻辑。\n- 棋盘在通知区与独立旋转区之间垂直居中。旋转区和道具区占据真实布局行,不覆盖棋盘。\n\n此方案使用独立的棋子造型、轨道材质和控件样式,共用现有弯曲轨道玩法。审核页支持同时保留多套方案。\n`); return `<section class="sheet-reference"><canvas id="sheet-reference-board" width="660" height="660" aria-label="从玩法实验页提取的双色棋盘,保留原有布局与画法"></canvas><div><span class="eyebrow">FROM VARIANTS / 画法提取</span><h2>线条轻一点,<br>棋子就清楚一点。</h2><p>以实验页「双色试航」的初始棋盘作对照:暖白格面、浅蓝灰轨道、陶橙与雾蓝棋子。边界只有一道细白线,箭头用圆头线条画出。</p><dl class="reference-spec"><dt>环境 / 棋盘</dt><dd>#EAF0F3 / #FAFBF7</dd><dt>轨道 / 分格</dt><dd>#B2C4CE / #D3DFE3</dd><dt>轨道线宽</dt><dd>格半径 × 0.055</dd><dt>棋子半径 / 白边</dt><dd>格半径 × 0.88 / 0.035</dd><dt>箭头</dt><dd>白色线条 · 圆头 · 无阴影</dd></dl><p>下方五种主题沿用这套棋盘、轨道与箭头画法。</p><a href="../../../${d.source.href}">查看原实验页 ↗</a></div></section><script type="application/json" id="sheet-reference-data">${JSON.stringify(referenceLevel).replaceAll('<','\\u003c')}</script>`;
} }
console.log('Art direction sheets built: 4 designs × 5 themes.'); for(const d of ART_DIRECTIONS){
const html=`<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${d.name} · HEX TRAILS 设计稿</title><link rel="stylesheet" href="../../showcase.css"><link rel="stylesheet" href="../../art-directions.css"><link rel="stylesheet" href="../../game-tools.css"></head><body class="sheet-page" data-design="${d.id}" style="--paper:${d.paper};--accent:${d.accent};--bg:${d.themes[0].background}"><main class="sheet-main"><nav class="sheet-nav"><a href="../../index.html">← 设计总览</a><span>第 ${d.code} 套 / 共 ${ART_DIRECTIONS.length} 套</span><a href="../../../design-showcase.html?design=${d.id}">试玩与审核 ↗</a></nav><nav class="sheet-family-nav" aria-label="切换设计方案">${ART_DIRECTIONS.map(other=>`<a href="../${other.id}/index.html"${other.id===d.id?' aria-current="page"':''}>${other.code} · ${other.name}</a>`).join('')}</nav><div class="sheet-agent-links"><a href="design.md">Agent 设计规范 · design.md ↗</a><a href="tokens.json">配色数据 · JSON ↗</a></div><header class="sheet-hero"><div><span class="eyebrow">${d.en}</span><h1>${d.name}</h1><p>${d.description}</p></div><canvas id="sheet-tokens" class="sheet-token-row" width="720" height="270" aria-label="${d.name}棋子造型设计"></canvas></header>${referenceStudy(d)}<section><div class="section-heading"><div><span class="eyebrow">01 / FIVE WORLDS</span><h2>同一风格,五种主题</h2></div><p>统一的弯曲轨道关卡 · 用实际渲染检查识别度</p></div><div class="sheet-themes">${d.themes.map(t=>`<article class="sheet-theme" style="--bg:${t.background};--ink:${t.ink}"><small>${t.label}</small><h3>${t.name}</h3><canvas data-sheet-theme="${t.id}" width="480" height="520" aria-label="${t.label}主题的弯曲棋盘"></canvas><div class="sheet-swatches">${t.palette.map(i=>`<i style="background:${d.pigments[i].color}" title="${d.pigments[i].color}"></i>`).join('')}</div><small>${t.background}</small></article>`).join('')}</div></section><section class="sheet-components"><div class="sheet-panel"><span class="eyebrow">02 / DESIGN LANGUAGE</span><h2>造型与交互规则</h2><ul>${d.principles.map(p=>`<li>${p}</li>`).join('')}<li>所有棋子保留独立方向箭头,颜色和表情不改变规则。</li><li>离场与碰撞共用游戏动画,切换设计保持路线与时长一致。</li></ul><p class="sheet-caption">这是一套可与其他方案同时保留的候选设计。到审核页勾选保留、记录修改意见;不会替换你的其他选择。</p><a class="sheet-link" href="../../../design-showcase.html?design=${d.id}">审核与体验这套设计 ↗</a></div><div class="sheet-panel"><span class="eyebrow">03 / COMPONENT STUDY</span><h2>控件与奖励</h2><div class="component-preview">${itemStudy(name=>itemIcon(name,'../../../'+ICON_ATLAS))}<div class="sample-modal"><div class="stars" aria-label="三颗金色星星">★ ★ ★</div><h3>漂亮,全部过关!</h3><p>每一次转弯,都找到出口。</p><a class="sheet-link" href="../../../design-showcase.html?design=${d.id}">继续旅程 →</a></div></div><p class="sheet-caption">底部道具独立于顶部导航。棋盘在通知与旋转区之间垂直居中;可操作棋盘在设计审核页。</p></div></section></main><script type="module" src="../../../src/direction-sheet.js"></script></body></html>`;
const packageOut=new URL(`${d.id}/`,out);
await mkdir(packageOut,{recursive:true});
// design.md is authored source; generation must never overwrite it.
await readFile(new URL('design.md',packageOut),'utf8');
await writeFile(new URL('index.html',packageOut),html);
await writeFile(new URL(`${d.id}.html`,out),`<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta http-equiv="refresh" content="0;url=${d.id}/index.html"><title>${d.name} · 设计稿已迁移</title></head><body><p>设计稿已整理到独立目录:<a href="${d.id}/index.html">${d.code} · ${d.name}</a></p></body></html>`);
await writeFile(new URL(`${d.id}.md`,out),`# ${d.code} · ${d.name}\n\n本文件为旧入口。完整、人工维护的 Agent 规范已迁移到 [${d.id}/design.md](${d.id}/design.md)。\n\n[设计稿](${d.id}/index.html) · [配色数据](${d.id}/tokens.json) · [Agent 接入说明](../README.md)\n`);
}
await buildDesignCatalog(ART_DIRECTIONS);
await buildDesignPackages(ART_DIRECTIONS);
console.log(`Art direction catalog and sheets built: ${ART_DIRECTIONS.length} designs × 5 themes.`);
+25
View File
@@ -0,0 +1,25 @@
import { writeFile } from 'node:fs/promises';
export async function buildDesignCatalog(directions) {
const latest = directions.at(-1), themes = directions[0].themes;
const html = `<!doctype html>
<html lang="zh-CN">
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>设计总览 · HEX TRAILS</title><link rel="stylesheet" href="catalog.css"></head>
<body>
<header class="catalog-nav"><a class="brand" href="../index.html">HEX TRAILS <span>设计档案</span></a><nav aria-label="设计文档入口"><a href="README.md">Agent 接入文档 ↗</a><a href="../design-showcase.html">多选与审核 ↗</a></nav></header>
<main>
<section class="catalog-intro"><div><span class="eyebrow">ART DIRECTION LIBRARY / 01–${latest.code}</span><h1>${directions.length} 套设计,<br>放在一起看。</h1><p>按方案编号整理,每套都有轻松、进阶、烧脑、挑战和每周挑战。<br>喜欢的可以同时保留,再到审核页逐套记录意见。</p></div><a class="latest-design" href="directions/${latest.id}/index.html"><span>最近新增 · 第 ${latest.code} 套</span><strong>${latest.name}</strong><small>${latest.description}</small><b>查看完整设计稿 ↗</b></a></section>
<section aria-labelledby="compare-title">
<div class="compare-heading"><div><span class="eyebrow">01 / COMPARE</span><h2 id="compare-title">同一棋盘,看清不同画法</h2><p>统一弯曲轨道与七色棋子;切换主题,同时更新全部方案。</p></div><a href="#theme-map">查看 ${directions.length * themes.length} 种主题 ↓</a></div>
<div class="catalog-theme-picker" role="group" aria-label="对比主题">${themes.map((t,i)=>`<button type="button" data-theme="${t.id}" aria-pressed="${i===0}">${t.label}</button>`).join('')}</div>
<p class="comparison-status" id="comparison-status" aria-live="polite">当前对比:轻松 · ${directions.length} 套设计</p>
<div class="catalog-grid">${directions.map(d=>`<article class="catalog-card" id="${d.id}"><div class="card-preview" style="background:${d.themes[0].background};color:${d.themes[0].ink}"><span class="design-number">${d.code} / ${d.tag}</span><canvas data-catalog-design="${d.id}" width="600" height="600" aria-label="${d.name} · 轻松主题棋盘"></canvas><span class="preview-theme">${d.themes[0].name}</span></div><div class="card-body"><span class="eyebrow">${d.en}</span><h3>${d.name}${d.id===latest.id?'<small>最新</small>':''}</h3><p>${d.description}</p><div class="card-actions"><a href="directions/${d.id}/index.html">完整设计稿 ↗</a><a data-review-design="${d.id}" href="../design-showcase.html?design=${d.id}&theme=lagoon#phone">试玩与审核 →</a></div></div></article>`).join('')}</div>
</section>
<section class="theme-map" id="theme-map" aria-labelledby="themes-title"><span class="eyebrow">02 / THEME INDEX</span><h2 id="themes-title">${directions.length} 套设计 × ${themes.length} 种主题</h2><p>每个名字对应一份配色。点击主题,直接进入对应方案的试玩与审核。</p><div class="theme-matrix">${directions.map(d=>`<article class="theme-family"><h3><a href="directions/${d.id}/index.html"><span>${d.code}</span> ${d.name} ↗</a></h3><div>${d.themes.map(t=>`<a class="theme-entry" href="../design-showcase.html?design=${d.id}&theme=${t.id}#phone"><i style="background:${t.background}" aria-hidden="true"></i><span><small>${t.label}</small><strong>${t.name}</strong></span><b aria-hidden="true">↗</b></a>`).join('')}</div></article>`).join('')}</div></section>
<section class="catalog-notes"><div><span class="eyebrow">03 / SHARED DETAILS</span><h2>已经统一的细节</h2></div><ul><li>棋盘垂直居中,适配手机、平板、折叠屏和桌面。</li><li>通知统一在剩余步数下方;底部三个道具放大,使用无阴影的纯色平面。</li><li>奖励星星为发光金色;次数和广告角标仅在设计稿中示意。</li><li>第 05 套保留浅色细轨道、细白边和线条箭头,去掉棋盘外围色标与棋子编号。</li></ul><a href="../Design.html">查看组件与交互规范 ↗</a></section>
</main>
<footer><span>HEX TRAILS · 设计方案 01–${latest.code}</span><a href="../design-showcase.html">选择方案、记录意见与导出审核 →</a></footer>
<script type="module" src="../src/design-catalog.js"></script>
</body></html>`;
await writeFile(new URL('../design/index.html', import.meta.url), html);
}
+74
View File
@@ -0,0 +1,74 @@
import { writeFile } from 'node:fs/promises';
import { THEME, contrast } from '../src/design-tokens.js';
import { directionTheme } from '../src/art-directions.js';
import { TILE_ATLAS } from '../src/sprites.js';
import { ICON_ATLAS } from '../src/ui-skin.js';
export const paletteCounts = [4, 7, 9, 12];
const materialKeys = ['board', 'boardGrid', 'track', 'trackHalo'];
const sources = {
directions: 'src/art-directions.js',
baseTokens: 'src/design-tokens.js',
renderer: 'src/renderer.js',
tokenDrawing: 'src/token-art.js',
componentStyles: 'design/art-directions.css',
itemStyles: 'design/game-tools.css',
};
export async function buildDesignPackages(directions) {
const manifest = {
schemaVersion: 1,
generated: true,
generatedBy: 'scripts/build-art-directions.mjs',
pathBase: 'repository-root',
entry: 'design/README.md',
agentGuide: 'design/AGENTS.md',
sharedRules: 'design/shared.md',
catalog: 'design/index.html',
example: 'design/examples/render-design.mjs',
defaultDesignId: 'calm',
latestDesignId: directions.at(-1).id,
selectionPolicy: 'multiple-candidates',
themeIds: directions[0].themes.map(t => t.id),
paletteCounts,
sources,
designs: directions.map(d => ({
id: d.id, code: d.code, name: d.name, artStyle: d.style,
status: d.id === 'calm' ? 'existing-default' : 'review-candidate',
directory: `design/directions/${d.id}`,
designDoc: `design/directions/${d.id}/design.md`,
tokens: `design/directions/${d.id}/tokens.json`,
preview: `design/directions/${d.id}/index.html`,
legacyPreview: `design/directions/${d.id}.html`,
})),
};
for (const direction of directions) {
const tokens = {
schemaVersion: 1,
generated: true,
generatedBy: 'scripts/build-art-directions.mjs',
pathBase: 'repository-root',
designId: direction.id,
artStyle: direction.style,
sources,
accent: direction.accent,
documentBackground: direction.paper,
sharedSemanticColors: THEME,
assets: { tileAtlas: direction.id === 'calm' ? TILE_ATLAS : null, uiIconAtlas: ICON_ATLAS },
pigments: direction.pigments.map((pigment, index) => ({ index, ...pigment })),
themes: direction.themes.map(theme => {
const resolved = { ...THEME, ...theme.materials };
const materials = Object.fromEntries(materialKeys.map(key => [key, resolved[key]]));
return {
id: theme.id, label: theme.label, name: theme.name,
background: theme.background, ink: theme.ink, materials,
anchorPalette: theme.palette,
palettes: Object.fromEntries(paletteCounts.map(count => [count, directionTheme(direction, theme.id, count).palette])),
contrast: { sceneText: contrast(theme.ink, theme.background), trackOnBoard: contrast(materials.track, materials.board) },
};
}),
};
await writeFile(new URL(`../design/directions/${direction.id}/tokens.json`, import.meta.url), JSON.stringify(tokens, null, 2) + '\n');
}
await writeFile(new URL('../design/manifest.json', import.meta.url), JSON.stringify(manifest, null, 2) + '\n');
}
+1
View File
@@ -38,6 +38,7 @@ const requestedTheme=LEVEL_THEMES.find(t=>t.id===new URLSearchParams(location.se
function applyTheme(score){ function applyTheme(score){
activeTheme=mode==='edit'||trial?null:directionTheme(requestedDesign,(requestedTheme||themeForScore(score)).id,Number(new URLSearchParams(location.search).get('colors'))||7); activeTheme=mode==='edit'||trial?null:directionTheme(requestedDesign,(requestedTheme||themeForScore(score)).id,Number(new URLSearchParams(location.search).get('colors'))||7);
const theme=activeTheme||LEVEL_THEMES[0]; const theme=activeTheme||LEVEL_THEMES[0];
document.body.dataset.theme=theme.id;
document.body.style.setProperty('--color-background',theme.background); document.body.style.setProperty('--color-background',theme.background);
document.body.style.setProperty('--scene-ink',theme.ink); document.body.style.setProperty('--scene-ink',theme.ink);
$('theme-caption').textContent=activeTheme?`${requestedTheme?'配色预览':theme.label} · ${theme.name}`:'自由配色 · 12 色棋子库'; $('theme-caption').textContent=activeTheme?`${requestedTheme?'配色预览':theme.label} · ${theme.name}`:'自由配色 · 12 色棋子库';
+17
View File
@@ -60,6 +60,23 @@ export const ART_DIRECTIONS=Object.freeze([
[[0,1,2,3,6,7,10],[1,4,8,5,2,6,11],[7,3,6,10,0,4,9],[8,5,4,2,7,10,1],[6,10,2,4,11,0,9]], [[0,1,2,3,6,7,10],[1,4,8,5,2,6,11],[7,3,6,10,0,4,9],[8,5,4,2,7,10,1],[6,10,2,4,11,0,9]],
['草地绿、奶油白与动物毛色,不使用糖果蓝紫。','秋叶黄配橡果棕、麦芽金和浅奶油,像一次林间秋游。','溪水青配苔绿与暖棕,保留自然界的冷暖关系。','红土与沙色小径,棕熊、奶油兔和绿蛙像野外探险队。','深森林绿配萤火浅黄,浅毛色角色从夜色中浮现。']), ['草地绿、奶油白与动物毛色,不使用糖果蓝紫。','秋叶黄配橡果棕、麦芽金和浅奶油,像一次林间秋游。','溪水青配苔绿与暖棕,保留自然界的冷暖关系。','红土与沙色小径,棕熊、奶油兔和绿蛙像野外探险队。','深森林绿配萤火浅黄,浅毛色角色从夜色中浮现。']),
pigments:makePigments(['#D9934F','#A4683B','#E1C58B','#6C9D40','#F1B869','#73503F','#F1DCC1','#448A67','#B78157','#BD784F','#F4E5CF','#87B76B'],['姜黄猫','栗棕熊','麦芽兔','苔绿蛙','奶橘猫','可可熊','奶油兔','溪绿蛙','榛果猫','赤陶熊','雪白兔','嫩叶蛙'])}, pigments:makePigments(['#D9934F','#A4683B','#E1C58B','#6C9D40','#F1B869','#73503F','#F1DCC1','#448A67','#B78157','#BD784F','#F4E5CF','#87B76B'],['姜黄猫','栗棕熊','麦芽兔','苔绿蛙','奶橘猫','可可熊','奶油兔','溪绿蛙','榛果猫','赤陶熊','雪白兔','嫩叶蛙'])},
{id:'paper',code:'05',name:'轻线纸境',en:'PAPER ROUTES',tag:'实验页衍生',style:'paper',accent:'#294D60',paper:'#F7F9F8',
description:'提取自玩法变种实验页:暖白纸面、轻盈细轨道、白边纯色六角与细线箭头。把颜色留给棋子,让弯路轻轻浮现。',
principles:['浅蓝灰细线与微小格缝,延续实验页的轻盈感','正六角纯色棋子、细白边、圆头线条箭头','无投影与渐变;陶橙和雾蓝是核心配色'],
source:{label:'玩法变种实验页',href:'labs/variants/index.html'},
themes:themes(
['#EAF0F3','#F3EEE5','#E8EFEB','#E7EBEE','#253B4A'],
['雾蓝信纸','暖砂手札','鼠尾草页','石墨练习','夜航图纸'],
[
{board:'#FAFBF7',boardGrid:'#D3DFE3',track:'#B2C4CE',trackHalo:'#FAFBF7'},
{board:'#FFFCF5',boardGrid:'#E3DCD0',track:'#C4B8A4',trackHalo:'#FFFCF5'},
{board:'#FAFCF8',boardGrid:'#D3DFD7',track:'#ADBCB2',trackHalo:'#FAFCF8'},
{board:'#F8FAFB',boardGrid:'#D1DBE1',track:'#A5B6C1',trackHalo:'#F8FAFB'},
{board:'#304A59',boardGrid:'#435E6C',track:'#849FAE',trackHalo:'#304A59'},
],
[[0,1,2,3,4,5,7],[0,1,3,6,8,9,4],[1,2,3,5,9,10,0],[0,1,4,6,7,10,11],[0,2,3,4,5,8,10]],
['保留实验页的雾蓝背景、暖白格面与陶橙/雾蓝棋子,扩展色同样保持哑光。','像暖砂色纸上的铅笔路线,陶橙、赭金与木棕带来温度。','浅鼠尾草纸底,灰绿轨道与蓝、苔绿棋子形成安静的冷暖关系。','灰白图纸与石墨蓝细线,用陶红、雾紫和钢蓝组织复杂路线。','深蓝灰图纸上保留细线与白箭头,暖色棋子像夜航中的小标记。']),
pigments:makePigments(['#D66C4D','#477FC0','#668877','#B58943','#8B7297','#45868C','#B76260','#607B90','#B28168','#46765F','#6B8AAB','#9C7284'],['陶橙','雾蓝','鼠尾草','赭金','雾紫','湖青','陶红','钢蓝','暖木','松绿','烟蓝','藕灰']).map(p=>({...p,arrow:'#FFFFFF'}))},
]); ]);
export function artDirection(id){return ART_DIRECTIONS.find(d=>d.id===id)||ART_DIRECTIONS[0];} export function artDirection(id){return ART_DIRECTIONS.find(d=>d.id===id)||ART_DIRECTIONS[0];}
export function directionTheme(direction,themeId,count=7){ export function directionTheme(direction,themeId,count=7){
+39
View File
@@ -0,0 +1,39 @@
import { ART_DIRECTIONS, directionTheme } from './art-directions.js';
import { drawScene, CanvasPainter } from './renderer.js';
import { layout } from './geometry.js';
import { createStudyLevel } from './study-levels.js';
import { TILE_ATLAS } from './sprites.js';
const level = createStudyLevel(3), atlas = new Image();
const themes = ART_DIRECTIONS[0].themes;
const requestedTheme = new URL(location.href).searchParams.get('theme');
let themeId = themes.some(t => t.id === requestedTheme) ? requestedTheme : themes[0].id;
function paint() {
for (const direction of ART_DIRECTIONS) {
const canvas = document.querySelector(`[data-catalog-design="${direction.id}"]`);
const theme = directionTheme(direction, themeId), painter = new CanvasPainter(canvas.getContext('2d'));
painter.atlas = atlas;
painter.ctx.clearRect(0, 0, canvas.width, canvas.height);
drawScene(painter, level, level.tiles, layout(level.cells, canvas.width, canvas.height), { theme });
canvas.setAttribute('aria-label', `${direction.name} · ${theme.label}主题棋盘`);
const preview = canvas.closest('.card-preview');
preview.style.background = theme.background;
preview.style.color = theme.ink;
preview.querySelector('.preview-theme').textContent = theme.name;
document.querySelector(`[data-review-design="${direction.id}"]`).href = `../design-showcase.html?design=${direction.id}&theme=${themeId}#phone`;
}
document.querySelectorAll('[data-theme]').forEach(button => button.setAttribute('aria-pressed', String(button.dataset.theme === themeId)));
document.querySelector('#comparison-status').textContent = `当前对比:${themes.find(t => t.id === themeId).label} · ${ART_DIRECTIONS.length} 套设计`;
}
document.querySelectorAll('[data-theme]').forEach(button => button.addEventListener('click', () => {
themeId = button.dataset.theme;
const url = new URL(location.href);
url.searchParams.set('theme', themeId);
history.replaceState(null, '', url);
paint();
}));
atlas.onload = paint;
atlas.src = '../' + TILE_ATLAS;
paint();
+1 -1
View File
@@ -13,7 +13,7 @@ export function mountDirectionReview(current,onSelect){
function select(d){ function select(d){
current=d;quick.value=d.id;document.body.dataset.design=d.id; current=d;quick.value=d.id;document.body.dataset.design=d.id;
picker.querySelectorAll('[data-direction]').forEach(b=>b.setAttribute('aria-pressed',String(b.dataset.direction===d.id))); picker.querySelectorAll('[data-direction]').forEach(b=>b.setAttribute('aria-pressed',String(b.dataset.direction===d.id)));
detail.innerHTML=`<div><span class="eyebrow">${d.en} / ${d.code}</span><h2>${d.name}</h2><p>${d.description}</p><ul>${d.principles.map(p=>`<li>${p}</li>`).join('')}</ul><a class="sheet-link" href="design/directions/${d.id}.html" target="_blank" rel="noopener">查看这套完整设计稿 ↗</a></div><label class="review-note">这套方案的审核意见<textarea id="direction-note" rows="3" placeholder="例如:保留棋子造型,轨道再轻一些……"></textarea><small id="review-saved">意见与多选结果保存在此浏览器</small></label>`; detail.innerHTML=`<div><span class="eyebrow">${d.en} / ${d.code}</span><h2>${d.name}</h2><p>${d.description}</p><ul>${d.principles.map(p=>`<li>${p}</li>`).join('')}</ul>${d.source?`<p>画法来源:<a href="${d.source.href}" target="_blank" rel="noopener">${d.source.label} ↗</a></p>`:''}<a class="sheet-link" href="design/directions/${d.id}/index.html" target="_blank" rel="noopener">查看这套完整设计稿 ↗</a></div><label class="review-note">这套方案的审核意见<textarea id="direction-note" rows="3" placeholder="例如:保留棋子造型,轨道再轻一些……"></textarea><small id="review-saved">意见与多选结果保存在此浏览器</small></label>`;
const note=detail.querySelector('textarea');note.value=typeof review.notes[d.id]==='string'?review.notes[d.id]:'';note.oninput=()=>{review.notes[d.id]=note.value;save();}; const note=detail.querySelector('textarea');note.value=typeof review.notes[d.id]==='string'?review.notes[d.id]:'';note.oninput=()=>{review.notes[d.id]=note.value;save();};
const url=new URL(location.href);url.searchParams.set('design',d.id);history.replaceState(null,'',url); const url=new URL(location.href);url.searchParams.set('design',d.id);history.replaceState(null,'',url);
onSelect(d); onSelect(d);
+8 -1
View File
@@ -8,5 +8,12 @@ function paint(){
const c=document.querySelector('#sheet-tokens'),p=new CanvasPainter(c.getContext('2d'));p.atlas=atlas;p.ctx.clearRect(0,0,c.width,c.height); const c=document.querySelector('#sheet-tokens'),p=new CanvasPainter(c.getContext('2d'));p.atlas=atlas;p.ctx.clearRect(0,0,c.width,c.height);
for(let i=0;i<4;i++)drawTile(p,105+i*165,135+(i%2?-12:12),83,appearanceTile({color:i,dir:1},directionTheme(direction,'lagoon'))); for(let i=0;i<4;i++)drawTile(p,105+i*165,135+(i%2?-12:12),83,appearanceTile({color:i,dir:1},directionTheme(direction,'lagoon')));
document.querySelectorAll('[data-sheet-theme]').forEach(c=>{const p=new CanvasPainter(c.getContext('2d'));p.atlas=atlas;drawScene(p,level,level.tiles,layout(level.cells,c.width,c.height),{theme:directionTheme(direction,c.dataset.sheetTheme)});}); document.querySelectorAll('[data-sheet-theme]').forEach(c=>{const p=new CanvasPainter(c.getContext('2d'));p.atlas=atlas;drawScene(p,level,level.tiles,layout(level.cells,c.width,c.height),{theme:directionTheme(direction,c.dataset.sheetTheme)});});
const reference=document.querySelector('#sheet-reference-board');
if(reference){
const sample=JSON.parse(document.querySelector('#sheet-reference-data').textContent),ctx=reference.getContext('2d'),p=new CanvasPainter(ctx);
const view={size:reference.width/9.3,ox:reference.width/2,oy:reference.height/2};
const theme={...directionTheme(direction,'lagoon'),palette:sample.tiles.map(t=>t.color)};
ctx.clearRect(0,0,reference.width,reference.height);drawScene(p,sample,sample.tiles,view,{theme});
}
} }
atlas.onload=paint;atlas.src=TILE_ATLAS.startsWith('assets/')?'../../'+TILE_ATLAS:TILE_ATLAS;paint(); atlas.onload=paint;atlas.src=new URL('../'+TILE_ATLAS,import.meta.url).href;paint();
+11 -11
View File
@@ -10,7 +10,7 @@ import { TILE_FRAMES, TILE_RADIUS, TILE_ASPECT } from './sprites.js';
export const COLORS=PIGMENTS.map(p=>p.color); export const COLORS=PIGMENTS.map(p=>p.color);
// Scene commands remain engine-independent. sprite() is an optional atlas adapter. // Scene commands remain engine-independent. sprite() is an optional atlas adapter.
export function drawTile(p,x,y,size,tile,angle=-Math.PI/2+tile.dir*Math.PI/3,scale=1) { export function drawTile(p,x,y,size,tile,angle=-Math.PI/2+tile.dir*Math.PI/3,scale=1) {
const r=size*TILE_RADIUS*scale; const r=size*(tile.artStyle==='paper'?.88:TILE_RADIUS)*scale;
if(drawStyledToken(p,x,y,r,tile,angle))return; if(drawStyledToken(p,x,y,r,tile,angle))return;
const textured=p.sprite?.(tile.color,x,y,r)===true; const textured=p.sprite?.(tile.color,x,y,r)===true;
if(!textured){ if(!textured){
@@ -25,6 +25,7 @@ export function drawScene(p,level,tiles,view,state={}) {
const {size}=view; const {size}=view;
const visualTile=tile=>appearanceTile({...tile,color:pigmentIndex(state.theme?Math.max(0,level.tiles.findIndex(t=>t.id===tile.id)):tile.color,state.theme)},state.theme); const visualTile=tile=>appearanceTile({...tile,color:pigmentIndex(state.theme?Math.max(0,level.tiles.findIndex(t=>t.id===tile.id)):tile.color,state.theme)},state.theme);
const materials={...THEME,...state.theme?.materials}; const materials={...THEME,...state.theme?.materials};
const paper=state.theme?.artStyle==='paper';
const angle=state.boardAngle||0,cos=Math.cos(angle),sin=Math.sin(angle); const angle=state.boardAngle||0,cos=Math.cos(angle),sin=Math.sin(angle);
const rotate=points=>points.map(pt=>{const x=pt.x-view.ox,y=pt.y-view.oy;return {x:view.ox+x*cos-y*sin,y:view.oy+x*sin+y*cos};}); const rotate=points=>points.map(pt=>{const x=pt.x-view.ox,y=pt.y-view.oy;return {x:view.ox+x*cos-y*sin,y:view.oy+x*sin+y*cos};});
const base={polygon:(pts,c)=>p.polygon(rotate(pts),c),line:(pts,c,w)=>p.line(rotate(pts),c,w)}; const base={polygon:(pts,c)=>p.polygon(rotate(pts),c),line:(pts,c,w)=>p.line(rotate(pts),c,w)};
@@ -43,14 +44,13 @@ export function drawScene(p,level,tiles,view,state={}) {
const movingIds=new Set(motions.map(a=>a.route.tile.id)); const movingIds=new Set(motions.map(a=>a.route.tile.id));
const cellKeys=new Set(level.cells.map(c=>key(c.q,c.r))); const cellKeys=new Set(level.cells.map(c=>key(c.q,c.r)));
const sorted=[...level.cells].sort((a,b)=>center(a,view).y-center(b,view).y); const sorted=[...level.cells].sort((a,b)=>center(a,view).y-center(b,view).y);
// Complete the contiguous board before drawing any rails. A tiny overlap // Paint cells before rails. Paper keeps the reference's narrow gaps; other
// prevents antialiasing seams between touching hexagons. // styles overlap slightly to avoid antialiasing seams.
for(const cell of sorted){const {x,y}=center(cell,view);base.polygon(hexPoints(x,y,size+.2),materials.board);} for(const cell of sorted){const {x,y}=center(cell,view);base.polygon(hexPoints(x,y,paper?size*.97:size+.2),materials.board);}
// Subtle cell boundaries beneath the rails: retain the hex grid without // Cell boundaries stay beneath the connected track endpoints.
// opening background gaps or painting over connected track endpoints.
for(const cell of sorted){ for(const cell of sorted){
const {x,y}=center(cell,view),edge=hexPoints(x,y,size); const {x,y}=center(cell,view),edge=hexPoints(x,y,paper?size*.97:size);
base.line([...edge,edge[0]],materials.boardGrid,Math.max(1,size*.024)); base.line([...edge,edge[0]],materials.boardGrid,paper?Math.max(.4,size*.025):Math.max(1,size*.024));
} }
const rails=[]; const rails=[];
for(const cell of sorted) { for(const cell of sorted) {
@@ -65,15 +65,15 @@ export function drawScene(p,level,tiles,view,state={}) {
const first=points[0],last=points[points.length-1],shrink=1-visualRadius/size; const first=points[0],last=points[points.length-1],shrink=1-visualRadius/size;
const dx0=exterior(a)?(first.x-x)*shrink:0,dy0=exterior(a)?(first.y-y)*shrink:0; const dx0=exterior(a)?(first.x-x)*shrink:0,dy0=exterior(a)?(first.y-y)*shrink:0;
const dx1=exterior(b)?(last.x-x)*shrink:0,dy1=exterior(b)?(last.y-y)*shrink:0; const dx1=exterior(b)?(last.x-x)*shrink:0,dy1=exterior(b)?(last.y-y)*shrink:0;
points.forEach((p,i)=>{const t=i/(points.length-1),u=1-t;p.x-=u*u*u*dx0+t*t*t*dx1;p.y-=u*u*u*dy0+t*t*t*dy1;}); if(!paper)points.forEach((p,i)=>{const t=i/(points.length-1),u=1-t;p.x-=u*u*u*dx0+t*t*t*dx1;p.y-=u*u*u*dy0+t*t*t*dy1;});
rails.push({points,primary:!state.trackEmphasis||state.trackEmphasis(cell,a,b)}); rails.push({points,primary:!state.trackEmphasis||state.trackEmphasis(cell,a,b)});
} }
} }
// Pattern studies can emphasize real routes while retaining the other paired // Pattern studies can emphasize real routes while retaining the other paired
// connections as secondary lines. Ordinary levels keep the same drawing passes. // connections as secondary lines. Ordinary levels keep the same drawing passes.
for(const {points,primary} of rails)if(!primary)base.line(points,state.secondaryTrackColor||materials.boardGrid,Math.max(1,size*.025)); for(const {points,primary} of rails)if(!primary)base.line(points,state.secondaryTrackColor||materials.boardGrid,Math.max(1,size*.025));
for(const {points,primary} of rails)if(primary)base.line(points,materials.trackHalo,Math.max(4,size*.16)); if(!paper)for(const {points,primary} of rails)if(primary)base.line(points,materials.trackHalo,Math.max(4,size*.16));
for(const {points,primary} of rails)if(primary)base.line(points,materials.track,Math.max(2.5,size*.10)); for(const {points,primary} of rails)if(primary)base.line(points,materials.track,paper?Math.max(.7,size*.055):Math.max(2.5,size*.10));
if(state.path) p.line(routePoints(state.path,view),state.path.status==='exit'?THEME.primary:THEME.warning,Math.max(4,size*.12)); if(state.path) p.line(routePoints(state.path,view),state.path.status==='exit'?THEME.primary:THEME.warning,Math.max(4,size*.12));
if(state.selected) { if(state.selected) {
const cell=level.cells.find(c=>c.q===state.selected.q&&c.r===state.selected.r); const cell=level.cells.find(c=>c.q===state.selected.q&&c.r===state.selected.r);
+3 -1
View File
@@ -20,7 +20,7 @@ const regularLevel=createStudyLevel(3);
let studyLevel=createStudyLevel(5); let studyLevel=createStudyLevel(5);
let colorCount=7; let colorCount=7;
let direction=artDirection(new URLSearchParams(location.search).get("design")); let direction=artDirection(new URLSearchParams(location.search).get("design"));
let theme=themeWithColors(LEVEL_THEMES[0],colorCount),game=new Game(studyLevel),animation=null,rotation=null,frame=0,view=null,hint=null,afterRotation=null; let theme=themeWithColors(LEVEL_THEMES.find(t=>t.id===new URLSearchParams(location.search).get('theme'))||LEVEL_THEMES[0],colorCount),game=new Game(studyLevel),animation=null,rotation=null,frame=0,view=null,hint=null,afterRotation=null;
const atlas=new Image();atlas.src=TILE_ATLAS;atlas.onload=()=>{painter.atlas=atlas;draw();paintSamples();}; const atlas=new Image();atlas.src=TILE_ATLAS;atlas.onload=()=>{painter.atlas=atlas;draw();paintSamples();};
function decorateIcons(){ function decorateIcons(){
document.querySelectorAll('[data-icon]').forEach(el=>{ document.querySelectorAll('[data-icon]').forEach(el=>{
@@ -35,8 +35,10 @@ $('pigment-grid').innerHTML=PIGMENTS.map((p,i)=>`<div class="pigment-item"><canv
$('quick-theme').innerHTML=LEVEL_THEMES.map(t=>`<option value="${t.id}">${t.label}</option>`).join(''); $('quick-theme').innerHTML=LEVEL_THEMES.map(t=>`<option value="${t.id}">${t.label}</option>`).join('');
$('quick-theme').onchange=()=>setTheme(direction.themes.find(t=>t.id===$('quick-theme').value)); $('quick-theme').onchange=()=>setTheme(direction.themes.find(t=>t.id===$('quick-theme').value));
function setTheme(next){ function setTheme(next){
const url=new URL(location.href);url.searchParams.set('theme',next.id);history.replaceState(null,'',url);
$('quick-theme').value=next.id; $('quick-theme').value=next.id;
theme=directionTheme(direction,next.id,colorCount);$('phone').style.setProperty('--scene-bg',theme.background);$('density-grid').style.setProperty('--bg',theme.background);$('density-grid').style.setProperty('--ink',theme.ink);$('phone').style.setProperty('--scene-ink',theme.ink); theme=directionTheme(direction,next.id,colorCount);$('phone').style.setProperty('--scene-bg',theme.background);$('density-grid').style.setProperty('--bg',theme.background);$('density-grid').style.setProperty('--ink',theme.ink);$('phone').style.setProperty('--scene-ink',theme.ink);
document.body.dataset.theme=theme.id;
$('item-study-content').style.setProperty('--scene-bg',theme.background);$('item-study-content').style.setProperty('--scene-ink',theme.ink); $('item-study-content').style.setProperty('--scene-bg',theme.background);$('item-study-content').style.setProperty('--scene-ink',theme.ink);
$('scene-label').textContent=`${theme.label} / ${theme.range}`;$('scene-name').textContent=theme.name; $('scene-label').textContent=`${theme.label} / ${theme.range}`;$('scene-name').textContent=theme.name;
$('scene-description').textContent=`${theme.description} 当前展示 ${colorCount} 色扩展搭配。`;$('phone-label').textContent=`${theme.label} · ${theme.name}`; $('scene-description').textContent=`${theme.description} 当前展示 ${colorCount} 色扩展搭配。`;$('phone-label').textContent=`${theme.label} · ${theme.name}`;
+9
View File
@@ -1,8 +1,17 @@
import { roundedHex } from './tile-shape.js'; import { roundedHex } from './tile-shape.js';
import { hexPoints } from './path-math.js';
export function tint(hex,amount){return '#'+hex.slice(1).match(/../g).map(v=>Math.round(amount>=0?parseInt(v,16)+(255-parseInt(v,16))*amount:parseInt(v,16)*(1+amount)).toString(16).padStart(2,'0')).join('');} export function tint(hex,amount){return '#'+hex.slice(1).match(/../g).map(v=>Math.round(amount>=0?parseInt(v,16)+(255-parseInt(v,16))*amount:parseInt(v,16)*(1+amount)).toString(16).padStart(2,'0')).join('');}
export function drawStyledToken(p,x,y,r,tile,angle){ export function drawStyledToken(p,x,y,r,tile,angle){
const fill=tile.fill,ink='#263A3A'; const fill=tile.fill,ink='#263A3A';
if(!fill||!tile.artStyle||tile.artStyle==='classic')return false; if(!fill||!tile.artStyle||tile.artStyle==='classic')return false;
if(tile.artStyle==='paper'){
const face=hexPoints(x,y,r),unit=r/.88;
p.polygon(face,fill);p.line([...face,face[0]],'#FFFFFFCC',unit*.035);
const rotate=points=>points.map(([u,v])=>({x:x+unit*(u*Math.cos(angle)-v*Math.sin(angle)),y:y+unit*(u*Math.sin(angle)+v*Math.cos(angle))}));
p.line(rotate([[-.3,0],[.3,0]]),tile.arrow,unit*.09);
p.line(rotate([[.08,-.17],[.3,0],[.08,.17]]),tile.arrow,unit*.09);
return true;
}
const face=roundedHex(x,y,r*.94),closed=[...face,face[0]]; const face=roundedHex(x,y,r*.94),closed=[...face,face[0]];
let arrowY=y,arrowScale=.58; let arrowY=y,arrowScale=.58;
if(tile.artStyle==='vivid'){ if(tile.artStyle==='vivid'){
+23 -4
View File
@@ -6,8 +6,26 @@ import { drawScene, drawTile, drawExit } from '../src/renderer.js';
import { createStudyLevel } from '../src/study-levels.js'; import { createStudyLevel } from '../src/study-levels.js';
import { available, trace } from '../src/core.js'; import { available, trace } from '../src/core.js';
import { layout } from '../src/geometry.js'; import { layout } from '../src/geometry.js';
test('each of four art directions has five readable themes and independent token colors',()=>{ test('paper study preserves the variants reference: pale thin rails and outlined hexagons with line arrows',()=>{
assert.equal(ART_DIRECTIONS.length,4);assert.equal(new Set(ART_DIRECTIONS.map(d=>d.id)).size,4); const theme=directionTheme(artDirection('paper'),'lagoon'),commands=[];
const p={polygon:(points,color)=>commands.push({kind:'polygon',points,color}),line:(points,color,width)=>commands.push({kind:'line',points,color,width}),circle:()=>{}};
assert.equal(theme.background,'#EAF0F3');assert.equal(theme.materials.board,'#FAFBF7');assert.equal(theme.materials.track,'#B2C4CE');
assert.deepEqual(theme.pigments.slice(0,2).map(p=>p.color),['#D66C4D','#477FC0']);
drawTile(p,50,50,40,appearanceTile({color:0,dir:0},theme));
assert.equal(commands[0].points.length,6);assert.equal(commands[0].color,'#D66C4D');
assert.ok(Math.abs(commands[0].points[0].x-50-40*.88)<1e-9);
assert.equal(commands[1].color,'#FFFFFFCC');assert.ok(Math.abs(commands[1].width-40*.035)<1e-9);
assert.deepEqual(commands.slice(2).map(c=>[c.kind,c.points.length,c.color]),[['line',2,'#FFFFFF'],['line',3,'#FFFFFF']]);
assert.ok(commands[2].points[1].y<commands[2].points[0].y,'direction zero still points up');
commands.length=0;
const level=createStudyLevel(3);drawScene(p,level,[],{size:40,ox:260,oy:260},{theme});
const rails=commands.filter(c=>c.kind==='line'&&c.color===theme.materials.track);
assert.equal(rails.length,level.cells.length*3);
assert.ok(rails.every(c=>Math.abs(c.width-40*.055)<1e-9));
assert.ok(!commands.some(c=>c.kind==='line'&&c.color===theme.materials.trackHalo),'no thick rail halo');
});
test('each of five art directions has five readable themes and independent token colors',()=>{
assert.equal(ART_DIRECTIONS.length,5);assert.equal(new Set(ART_DIRECTIONS.map(d=>d.id)).size,5);
for(const d of ART_DIRECTIONS){ for(const d of ART_DIRECTIONS){
assert.deepEqual(d.themes.map(t=>t.label),['轻松','进阶','烧脑','挑战','每周挑战']); assert.deepEqual(d.themes.map(t=>t.label),['轻松','进阶','烧脑','挑战','每周挑战']);
for(const t of d.themes){assert.ok(contrast(t.background,t.ink)>=4.5);assert.equal(directionTheme(d,t.id).artStyle,d.style);} for(const t of d.themes){assert.ok(contrast(t.background,t.ink)>=4.5);assert.equal(directionTheme(d,t.id).artStyle,d.style);}
@@ -24,7 +42,7 @@ test('art styles change actual geometry without changing level state, including
drawTile(p,50,50,40,t);signatures.push(JSON.stringify(commands)); drawTile(p,50,50,40,t);signatures.push(JSON.stringify(commands));
drawScene(p,level,level.tiles,view,{theme});drawExit(p,route,view,.4,t);drawExit(p,route,view,.99,t); drawScene(p,level,level.tiles,view,{theme});drawExit(p,route,view,.4,t);drawExit(p,route,view,.99,t);
} }
assert.equal(new Set(signatures).size,4);assert.equal(JSON.stringify(level),copy); assert.equal(new Set(signatures).size,5);assert.equal(JSON.stringify(level),copy);
}); });
test('vivid worlds retain strong chroma and each new theme has its own seven-color mix',()=>{ test('vivid worlds retain strong chroma and each new theme has its own seven-color mix',()=>{
const vivid=artDirection('vivid'); const vivid=artDirection('vivid');
@@ -36,6 +54,7 @@ test('vivid worlds retain strong chroma and each new theme has its own seven-col
for(const d of ART_DIRECTIONS.slice(1)){ for(const d of ART_DIRECTIONS.slice(1)){
assert.equal(new Set(d.themes.map(t=>directionTheme(d,t.id).palette.slice().sort((a,b)=>a-b).join(','))).size,5); assert.equal(new Set(d.themes.map(t=>directionTheme(d,t.id).palette.slice().sort((a,b)=>a-b).join(','))).size,5);
assert.equal(new Set(d.themes.map(t=>t.materials.board)).size,5); assert.equal(new Set(d.themes.map(t=>t.materials.board)).size,5);
for(const t of d.themes)assert.ok(contrast(t.materials.track,t.materials.board)>=3); // The paper study deliberately retains the reference's low-contrast rails.
if(d.style!=='paper')for(const t of d.themes)assert.ok(contrast(t.materials.track,t.materials.board)>=3);
} }
}); });
+84
View File
@@ -0,0 +1,84 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { readFile, access } from 'node:fs/promises';
import { ART_DIRECTIONS, directionTheme } from '../src/art-directions.js';
import { THEME } from '../src/design-tokens.js';
import { recordDesign } from '../design/examples/render-design.mjs';
const root = new URL('../', import.meta.url);
const read = path => readFile(new URL(path, root), 'utf8');
const manifest = JSON.parse(await read('design/manifest.json'));
test('an agent can discover all authored specs, resolved tokens, previews and implementation sources', async () => {
assert.equal(manifest.schemaVersion, 1);
assert.equal(manifest.pathBase, 'repository-root');
assert.deepEqual(manifest.designs.map(d => d.id), ART_DIRECTIONS.map(d => d.id));
assert.equal(manifest.defaultDesignId, 'calm');
for (const path of [manifest.entry, manifest.agentGuide, manifest.sharedRules, manifest.catalog, manifest.example, ...Object.values(manifest.sources)]) {
await access(new URL(path, root));
}
for (const entry of manifest.designs) {
const spec = await read(entry.designDoc);
assert.match(spec, new RegExp('设计 ID:`' + entry.id + '`'));
assert.ok(spec.includes('../../shared.md'));
assert.ok(spec.includes('tokens.json'));
assert.ok(spec.includes('## 验收与接入'));
const html = await read(entry.preview);
assert.ok(html.includes(`data-design="${entry.id}"`));
assert.ok(html.includes('href="design.md"'));
assert.ok(html.includes('href="tokens.json"'));
assert.ok((await read(entry.legacyPreview)).includes(`url=${entry.id}/index.html`));
assert.ok((await read(`design/directions/${entry.id}.md`)).includes(`${entry.id}/design.md`));
}
});
test('generated design data stays aligned with runtime palettes at every supported color count', async () => {
for (const entry of manifest.designs) {
const direction = ART_DIRECTIONS.find(d => d.id === entry.id);
const data = JSON.parse(await read(entry.tokens));
assert.equal(data.designId, entry.id);
assert.equal(data.artStyle, direction.style);
assert.deepEqual(data.pigments.map(({ index, ...pigment }) => pigment), direction.pigments);
assert.deepEqual(data.themes.map(t => t.id), manifest.themeIds);
for (const theme of data.themes) {
const source = direction.themes.find(t => t.id === theme.id);
assert.equal(theme.background, source.background);
assert.equal(theme.ink, source.ink);
for (const [key, value] of Object.entries(theme.materials)) assert.equal(value, source.materials?.[key] ?? THEME[key]);
for (const count of manifest.paletteCounts) assert.deepEqual(theme.palettes[count], directionTheme(direction, theme.id, count).palette);
}
}
});
test('canonical design documents and previews have no broken local links after the directory move', async () => {
const paths = ['design/README.md', 'design/AGENTS.md', 'design/shared.md', 'design/index.html', ...manifest.designs.flatMap(d => [d.designDoc, d.preview, d.legacyPreview])];
for (const path of paths) {
const source = (await read(path)).replace(/```[\s\S]*?```/g, '');
const links = path.endsWith('.md') ? [...source.matchAll(/\[[^\]]+\]\(([^)]+)\)/g)] : [...source.matchAll(/(?:href|src)="([^"]+)"/g)];
for (const [, href] of links) {
if (/^(?:https?:|data:|#)/.test(href)) continue;
const target = new URL(href, new URL(path, root));
target.search = ''; target.hash = '';
await assert.doesNotReject(access(target), `${path} → ${href}`);
}
}
});
test('the documented integration example renders all design and theme combinations and rejects typos', () => {
for (const direction of ART_DIRECTIONS) {
for (const theme of direction.themes) {
const result = recordDesign(direction.id, theme.id, 7);
assert.equal(result.designId, direction.id);
assert.equal(result.background, theme.background);
assert.ok(result.commands.some(command => command.type === 'line'));
assert.ok(result.commands.some(command => command.type === 'polygon'));
for (const command of result.commands) {
if (command.points) assert.ok(command.points.every(point => Number.isFinite(point.x) && Number.isFinite(point.y)));
if (command.width !== undefined) assert.ok(command.width > 0);
}
}
}
assert.throws(() => recordDesign('paepr'), /Unknown design/);
assert.throws(() => recordDesign('paper', 'night'), /Unknown theme/);
assert.throws(() => recordDesign('paper', 'lagoon', 5), /Unsupported color count/);
});