diff --git a/AGENTS.md b/AGENTS.md index 9079e4b..c221abd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -36,3 +36,7 @@ Before calling MCP tools, read `/Users/wukaibing/.grok-go/agents-guide.md` for v ### 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. + +### 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//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. diff --git a/Design.html b/Design.html index c8f495b..b2384ed 100644 --- a/Design.html +++ b/Design.html @@ -108,7 +108,8 @@ body:not(.editing) .board-tools>.game-tool{grid-row:3;justify-self:center;width:
05 / MOTION

轻快地出现,清楚地反馈

打开演示,查看遮罩、弹窗入场和三颗星星的顺序。遵循系统“减少动态效果”设置。

漂亮,全部过关!

按钮按压150 ms
弹窗从上方进入420 ms
星星依次开始300 / 490 / 680 ms
碰撞反馈总长1250 ms

碰撞顺序:接近 → 接触停顿与阻挡棋子轻震 → 原路弹回。该顺序在游戏中执行,此处只演示弹窗;游戏音效在首次手势后启动,可在设置关闭。

06 / FULL SPECIFICATION

完整设计规范

以下内容自动来自 Design.md。颜色数值来自共享 Token;修改源文件并运行 npm run design 即可重新生成本手册。

HEX TRAILS 设计规范

版本: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. 设计方向

清爽、明快、易读的哑光解谜游戏。 环境使用按难度或活动组织的低饱和主题色,棋盘采用暖白,主操作采用紫色。全局建立 12 色棋子库,单关默认使用其中 7 色,支持 4 / 7 / 9 / 12 色对照;高饱和度不平均分配给每一个按钮。

  • 棋子是第一视觉焦点,Level 和步数是第二层,工具是第三层。
  • 纯色背景,无纹理、光晕、装饰圆盘。底板要看得出六边形分格。
  • 棋子保持正顶视角、六边等长、小圆角;无侧面厚度和凹槽。
  • 图标采用用户选定的生成图集原始造型,不得静默替换为另一套代码图形。
  • 扁平面板搭配生成图标;保留图标原图细节,不用黑化、白化滤镜重塑外观。
@@ -141,9 +142,9 @@ body:not(.editing) .board-tools>.game-tool{grid-row:3;justify-self:center;width:

游戏主界面

游戏顶部恢复居中关卡计数,左侧选关、右侧设置;剩余步数位于其下方,不显示主题说明。棋盘视口占据顶部通知与独立旋转区之间的可用空间,棋盘在该区域垂直居中,不再限制为正方形。左右转与底部道具分别占据真实布局行,不覆盖棋盘。底部仅排列撤销、重来、提示。缩放按钮与常驻手势说明不占用棋盘空间;保留双指缩放、放大后拖动及桌面组合键滚轮。左侧选关,右侧设置。没有 Logo、关卡副标题、难度、待离场数量、角度读数或底部导航。

旋转操作为放大的生成箭头,下方分别写“左转”“右转”,不加矩形底板。文字用主文字色;白色箭头允许轻微蓝灰轮廓阴影增强边缘识别,不能改写原图轮廓。它们须保留至少 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 次”和“广告获取提示”的外观,不接入库存、扣次数、广告播放或奖励。实际次数、定价与获取方式待产品确定。

-

统一审核页的“道具设计”区域、四套独立设计稿与离线手册均包含普通态和未来角标示例,允许在同一页面切换比较。

+

统一审核页的“道具设计”区域、五套独立设计稿与离线手册均包含普通态和未来角标示例,允许在同一页面切换比较。

棋盘和轨道

大棋盘测试支持边长 6(轴向半径 5,91 格)与边长 7(半径 6,127 格)。网页棋盘支持以双指中点为锚的 1–4 倍缩放,放大后单指拖动;双指手势及其余指抬起不触发棋子点击。100% 视图保留原有边缘拖动旋转,放大后用左右按钮旋转。桌面提供 Ctrl/Command + 滚轮缩放;收拢双指或缩小回 100% 恢复全盘视图。缩放与绘制、点击共用同一坐标变换,不改变规则坐标。

先画全部底格,再画分格线,再统一画轨道外缘与中心线,最后画棋子。相邻格子连接端口必须完全重合。内部不留透出背景的缝;外边缘需为线帽预留空间。分格线宽取 max(1px, 格子半径 × 0.024)。

@@ -171,14 +172,19 @@ body:not(.editing) .board-tools>.game-tool{grid-row:3;justify-self:center;width:
  1. 明确修改哪个语义角色,优先复用现有角色。
  2. 修改 src/design-tokens.js,运行 npm run design。
  3. 检查自动生成的 PNG/CSS/JSON 和对比度报告。
  4. 运行 npm test、npm run build。
  5. 对五套背景逐一检查场景文字与箭头,并检查离场、旋转和撤销后颜色稳定。检查 390×844 手机布局,以及较矮屏幕;检查主界面、选关、设置、通关/失败和碰撞状态。
  6. 验证弹窗不能穿透操作、图标居中、轨道接缝不断裂。
  7. 如需替换已接受的图片,明确告知用户素材来源和变化,再更新本表。

Cocos 读取同一 JSON/JS token 和 PNG。CandyUiSkin 使用纯色 Sprite、圆角 Mask 和居中的图标;不得在原生侧另外猜颜色。本规范与适配组件已提供,原生场景尚需导入后做视觉验收。

多设计方案审核

-

新增四套并列设计(现有方案 + 三套实验方案),每套均包含轻松、进阶、烧脑、挑战、每周挑战五种主题。设计切换同时作用于棋子造型、轨道材质与主题配色;共享同一弯曲关卡与动画逻辑。

-
设计视觉特征独立设计稿
柔和原色哑光色块、蓝灰轨道[柔和原色](design/directions/calm.html)
高彩游乐场高饱和撞色、粗轮廓、贴纸硬阴影[高彩游乐场](design/directions/vivid.html)
积木轨道屋塑胶高光、分层厚度、圆角积木[积木轨道屋](design/directions/toy.html)
动物弯弯镇猫、熊、兔、蛙造型与独立箭头[动物弯弯镇](design/directions/animals.html)
-

在 [统一审核页](design-showcase.html) 切换设计及主题,可同时保留多套、逐套记录意见并导出 JSON。意见保存在当前浏览器。设计稿由 scripts/build-art-directions.mjs 生成,颜色及造型规则来自 src/art-directions.js、src/token-art.js。

+

Agent 接入先读 [design/README.md](design/README.md),共用规则见 [design/shared.md](design/shared.md)。每套独立目录包含人工维护的 design.md、生成配色 tokens.json 和预览 index.html;五套方案不能只用本手册的基础色值描述。

+

共有五套并列设计(现有方案 + 三套实验方案 + 玩法实验页衍生方案),每套均包含轻松、进阶、烧脑、挑战、每周挑战五种主题。设计切换同时作用于棋子造型、轨道材质与主题配色;共享同一弯曲关卡与动画逻辑。

+
编号设计视觉特征独立设计稿
01柔和原色哑光色块、蓝灰轨道[柔和原色](design/directions/calm/index.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。

+

轻线纸境 · 实验页画法提取

+

来源为 [玩法变种实验页](labs/variants/index.html) 的棋盘绘制,不改动实验页的关卡或玩法。独立设计稿中以「双色试航」的初始布局作对照,保留陶橙/雾蓝棋子、细白边与线条箭头;统一审核页使用与其他方案相同的弯曲轨道测试关,供多选比较。

+

通关弹窗 · 动效示例

漂亮,全部过关!

背景已变暗、模糊,并停止响应操作。
三颗星星依次出现。

- + \ No newline at end of file +动物弯弯镇 · 设计稿已迁移

设计稿已整理到独立目录:04 · 动物弯弯镇

\ No newline at end of file diff --git a/design/directions/animals.md b/design/directions/animals.md index fa2ed16..c347f7b 100644 --- a/design/directions/animals.md +++ b/design/directions/animals.md @@ -1,27 +1,5 @@ -# 动物弯弯镇 +# 04 · 动物弯弯镇 -猫咪、小熊、兔子与青蛙沿轨道出游。用表情与耳朵增加性格,方向箭头独立保留。 +本文件为旧入口。完整、人工维护的 Agent 规范已迁移到 [animals/design.md](animals/design.md)。 -## 设计规则 - -- 圆脸、短耳与小表情,外形保持在六角范围内 -- 箭头留在脸部下方,不靠动物朝向猜方向 -- 草地、木棕与奶油毛色,搭配森林小径 - -## 五种主题 - -| 难度 | 名称 | 环境色 | -| --- | --- | --- | -| 轻松 | 青草野餐 | #A9CD69 | -| 进阶 | 橡果秋游 | #E7B947 | -| 烧脑 | 溪边捉迷藏 | #72BEB2 | -| 挑战 | 红土探险 | #C67B47 | -| 每周挑战 | 萤火森林 | #183F36 | - -## 游戏道具与布局 - -- 撤销、重来、提示使用独立彩色底座,64–68px,图标 32px,标签 13px。顶部导航保持紧凑。道具使用纯色平面,不使用阴影、立体底边或渐变高光。 -- 右上角预留次数或“▶ AD”角标。设计稿展示“撤销 3 次”和“广告获取提示”,仅为样式,不启用真实库存或广告逻辑。 -- 棋盘在通知区与独立旋转区之间垂直居中。旋转区和道具区占据真实布局行,不覆盖棋盘。 - -此方案使用独立的棋子造型、轨道材质和控件样式,共用现有弯曲轨道玩法。审核页支持同时保留多套方案。 +[设计稿](animals/index.html) · [配色数据](animals/tokens.json) · [Agent 接入说明](../README.md) diff --git a/design/directions/animals/design.md b/design/directions/animals/design.md new file mode 100644 index 0000000..b920020 --- /dev/null +++ b/design/directions/animals/design.md @@ -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 示例未完成此套角色的实机验收。 diff --git a/design/directions/animals/index.html b/design/directions/animals/index.html new file mode 100644 index 0000000..957a168 --- /dev/null +++ b/design/directions/animals/index.html @@ -0,0 +1 @@ +动物弯弯镇 · HEX TRAILS 设计稿
PAW PARADE

动物弯弯镇

猫咪、小熊、兔子与青蛙沿轨道出游。用表情与耳朵增加性格,方向箭头独立保留。

01 / FIVE WORLDS

同一风格,五种主题

统一的弯曲轨道关卡 · 用实际渲染检查识别度

轻松

青草野餐

#A9CD69
进阶

橡果秋游

#E7B947
烧脑

溪边捉迷藏

#72BEB2
挑战

红土探险

#C67B47
每周挑战

萤火森林

#183F36
02 / DESIGN LANGUAGE

造型与交互规则

  • 圆脸、短耳与小表情,外形保持在六角范围内
  • 箭头留在脸部下方,不靠动物朝向猜方向
  • 草地、木棕与奶油毛色,搭配森林小径
  • 所有棋子保留独立方向箭头,颜色和表情不改变规则。
  • 离场与碰撞共用游戏动画,切换设计保持路线与时长一致。

这是一套可与其他方案同时保留的候选设计。到审核页勾选保留、记录修改意见;不会替换你的其他选择。

审核与体验这套设计 ↗
03 / COMPONENT STUDY

控件与奖励

撤销
重来
提示

道具底座 64–68px,图标 32px,标签 13px。用独立造型、大小和功能色,与顶部导航按钮区分;纯色平面,无阴影或渐变高光。

FUTURE STATES / 仅设计示意

为次数与广告留出位置

3撤销
重来
AD提示

右上角展示剩余次数,或播放符号 + AD。无角标表示普通状态;数字和获取方式均为样式示例,当前游戏不启用。

★ ★ ★

漂亮,全部过关!

每一次转弯,都找到出口。

继续旅程 →

底部道具独立于顶部导航。棋盘在通知与旋转区之间垂直居中;可操作棋盘在设计审核页。

\ No newline at end of file diff --git a/design/directions/animals/tokens.json b/design/directions/animals/tokens.json new file mode 100644 index 0000000..a11ab4a --- /dev/null +++ b/design/directions/animals/tokens.json @@ -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 + } + } + ] +} diff --git a/design/directions/calm.html b/design/directions/calm.html index f9b1bb2..41b2b87 100644 --- a/design/directions/calm.html +++ b/design/directions/calm.html @@ -1 +1 @@ -柔和原色 · HEX TRAILS 设计稿
QUIET TRAILS

柔和原色

低饱和环境与平面六角棋子,让注意力留在轨道和方向。

01 / FIVE WORLDS

同一风格,五种主题

统一的弯曲轨道关卡 · 用实际渲染检查识别度

轻松

薄荷浅湾

#C5E4DF
进阶

杏色午后

#F1D7B8
烧脑

鸢尾花园

#D8D0EB
挑战

玫瑰暮色

#E7C3CE
每周挑战

午夜极光

#293956
02 / DESIGN LANGUAGE

造型与交互规则

  • 哑光色块,克制装饰
  • 蓝灰细轨道,保持视觉留白
  • 金色奖励,柔和浮现
  • 所有棋子保留独立方向箭头,颜色和表情不改变规则。
  • 离场与碰撞共用游戏动画,切换设计保持路线与时长一致。

这是一套可与其他方案同时保留的候选设计。到审核页勾选保留、记录修改意见;不会替换你的其他选择。

审核与体验这套设计 ↗
03 / COMPONENT STUDY

控件与奖励

撤销
重来
提示

道具底座 64–68px,图标 32px,标签 13px。用独立造型、大小和功能色,与顶部导航按钮区分;纯色平面,无阴影或渐变高光。

FUTURE STATES / 仅设计示意

为次数与广告留出位置

3撤销
重来
AD提示

右上角展示剩余次数,或播放符号 + AD。无角标表示普通状态;数字和获取方式均为样式示例,当前游戏不启用。

★ ★ ★

漂亮,全部过关!

每一次转弯,都找到出口。

继续旅程 →

底部道具独立于顶部导航。棋盘在通知与旋转区之间垂直居中;可操作棋盘在设计审核页。

\ No newline at end of file +柔和原色 · 设计稿已迁移

设计稿已整理到独立目录:01 · 柔和原色

\ No newline at end of file diff --git a/design/directions/calm.md b/design/directions/calm.md index 593fc9b..ed113f6 100644 --- a/design/directions/calm.md +++ b/design/directions/calm.md @@ -1,27 +1,5 @@ -# 柔和原色 +# 01 · 柔和原色 -低饱和环境与平面六角棋子,让注意力留在轨道和方向。 +本文件为旧入口。完整、人工维护的 Agent 规范已迁移到 [calm/design.md](calm/design.md)。 -## 设计规则 - -- 哑光色块,克制装饰 -- 蓝灰细轨道,保持视觉留白 -- 金色奖励,柔和浮现 - -## 五种主题 - -| 难度 | 名称 | 环境色 | -| --- | --- | --- | -| 轻松 | 薄荷浅湾 | #C5E4DF | -| 进阶 | 杏色午后 | #F1D7B8 | -| 烧脑 | 鸢尾花园 | #D8D0EB | -| 挑战 | 玫瑰暮色 | #E7C3CE | -| 每周挑战 | 午夜极光 | #293956 | - -## 游戏道具与布局 - -- 撤销、重来、提示使用独立彩色底座,64–68px,图标 32px,标签 13px。顶部导航保持紧凑。道具使用纯色平面,不使用阴影、立体底边或渐变高光。 -- 右上角预留次数或“▶ AD”角标。设计稿展示“撤销 3 次”和“广告获取提示”,仅为样式,不启用真实库存或广告逻辑。 -- 棋盘在通知区与独立旋转区之间垂直居中。旋转区和道具区占据真实布局行,不覆盖棋盘。 - -此方案使用独立的棋子造型、轨道材质和控件样式,共用现有弯曲轨道玩法。审核页支持同时保留多套方案。 +[设计稿](calm/index.html) · [配色数据](calm/tokens.json) · [Agent 接入说明](../README.md) diff --git a/design/directions/calm/design.md b/design/directions/calm/design.md new file mode 100644 index 0000000..5476b36 --- /dev/null +++ b/design/directions/calm/design.md @@ -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 示例可参考图集适配,但五套设计切换与当前动画尚未完成原生验收。 diff --git a/design/directions/calm/index.html b/design/directions/calm/index.html new file mode 100644 index 0000000..1d5bd82 --- /dev/null +++ b/design/directions/calm/index.html @@ -0,0 +1 @@ +柔和原色 · HEX TRAILS 设计稿
QUIET TRAILS

柔和原色

低饱和环境与平面六角棋子,让注意力留在轨道和方向。

01 / FIVE WORLDS

同一风格,五种主题

统一的弯曲轨道关卡 · 用实际渲染检查识别度

轻松

薄荷浅湾

#C5E4DF
进阶

杏色午后

#F1D7B8
烧脑

鸢尾花园

#D8D0EB
挑战

玫瑰暮色

#E7C3CE
每周挑战

午夜极光

#293956
02 / DESIGN LANGUAGE

造型与交互规则

  • 哑光色块,克制装饰
  • 蓝灰细轨道,保持视觉留白
  • 金色奖励,柔和浮现
  • 所有棋子保留独立方向箭头,颜色和表情不改变规则。
  • 离场与碰撞共用游戏动画,切换设计保持路线与时长一致。

这是一套可与其他方案同时保留的候选设计。到审核页勾选保留、记录修改意见;不会替换你的其他选择。

审核与体验这套设计 ↗
03 / COMPONENT STUDY

控件与奖励

撤销
重来
提示

道具底座 64–68px,图标 32px,标签 13px。用独立造型、大小和功能色,与顶部导航按钮区分;纯色平面,无阴影或渐变高光。

FUTURE STATES / 仅设计示意

为次数与广告留出位置

3撤销
重来
AD提示

右上角展示剩余次数,或播放符号 + AD。无角标表示普通状态;数字和获取方式均为样式示例,当前游戏不启用。

★ ★ ★

漂亮,全部过关!

每一次转弯,都找到出口。

继续旅程 →

底部道具独立于顶部导航。棋盘在通知与旋转区之间垂直居中;可操作棋盘在设计审核页。

\ No newline at end of file diff --git a/design/directions/calm/tokens.json b/design/directions/calm/tokens.json new file mode 100644 index 0000000..f93c9a9 --- /dev/null +++ b/design/directions/calm/tokens.json @@ -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 + } + } + ] +} diff --git a/design/directions/paper.html b/design/directions/paper.html new file mode 100644 index 0000000..c5bd089 --- /dev/null +++ b/design/directions/paper.html @@ -0,0 +1 @@ +轻线纸境 · 设计稿已迁移

设计稿已整理到独立目录:05 · 轻线纸境

\ No newline at end of file diff --git a/design/directions/paper.md b/design/directions/paper.md new file mode 100644 index 0000000..a105872 --- /dev/null +++ b/design/directions/paper.md @@ -0,0 +1,5 @@ +# 05 · 轻线纸境 + +本文件为旧入口。完整、人工维护的 Agent 规范已迁移到 [paper/design.md](paper/design.md)。 + +[设计稿](paper/index.html) · [配色数据](paper/tokens.json) · [Agent 接入说明](../README.md) diff --git a/design/directions/paper/design.md b/design/directions/paper/design.md new file mode 100644 index 0000000..08fb336 --- /dev/null +++ b/design/directions/paper/design.md @@ -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 的棋子和轨道画法。 + +- 对照实验页的细线与六角比例;参考布局没有外围色标与编号。 +- 检查每个主题的长弯路,移动中仍为开放线条箭头、细白边棋子。 +- 检查手机小图上的细线不断裂,以及午夜主题的旋转按钮颜色。 +- 原生适配需检查圆头线端、透明细白边与格缝;尚未完成原生实机验收。 diff --git a/design/directions/paper/index.html b/design/directions/paper/index.html new file mode 100644 index 0000000..bc46c03 --- /dev/null +++ b/design/directions/paper/index.html @@ -0,0 +1 @@ +轻线纸境 · HEX TRAILS 设计稿
PAPER ROUTES

轻线纸境

提取自玩法变种实验页:暖白纸面、轻盈细轨道、白边纯色六角与细线箭头。把颜色留给棋子,让弯路轻轻浮现。

FROM VARIANTS / 画法提取

线条轻一点,
棋子就清楚一点。

以实验页「双色试航」的初始棋盘作对照:暖白格面、浅蓝灰轨道、陶橙与雾蓝棋子。边界只有一道细白线,箭头用圆头线条画出。

环境 / 棋盘
#EAF0F3 / #FAFBF7
轨道 / 分格
#B2C4CE / #D3DFE3
轨道线宽
格半径 × 0.055
棋子半径 / 白边
格半径 × 0.88 / 0.035
箭头
白色线条 · 圆头 · 无阴影

下方五种主题沿用这套棋盘、轨道与箭头画法。

查看原实验页 ↗
01 / FIVE WORLDS

同一风格,五种主题

统一的弯曲轨道关卡 · 用实际渲染检查识别度

轻松

雾蓝信纸

#EAF0F3
进阶

暖砂手札

#F3EEE5
烧脑

鼠尾草页

#E8EFEB
挑战

石墨练习

#E7EBEE
每周挑战

夜航图纸

#253B4A
02 / DESIGN LANGUAGE

造型与交互规则

  • 浅蓝灰细线与微小格缝,延续实验页的轻盈感
  • 正六角纯色棋子、细白边、圆头线条箭头
  • 无投影与渐变;陶橙和雾蓝是核心配色
  • 所有棋子保留独立方向箭头,颜色和表情不改变规则。
  • 离场与碰撞共用游戏动画,切换设计保持路线与时长一致。

这是一套可与其他方案同时保留的候选设计。到审核页勾选保留、记录修改意见;不会替换你的其他选择。

审核与体验这套设计 ↗
03 / COMPONENT STUDY

控件与奖励

撤销
重来
提示

道具底座 64–68px,图标 32px,标签 13px。用独立造型、大小和功能色,与顶部导航按钮区分;纯色平面,无阴影或渐变高光。

FUTURE STATES / 仅设计示意

为次数与广告留出位置

3撤销
重来
AD提示

右上角展示剩余次数,或播放符号 + AD。无角标表示普通状态;数字和获取方式均为样式示例,当前游戏不启用。

★ ★ ★

漂亮,全部过关!

每一次转弯,都找到出口。

继续旅程 →

底部道具独立于顶部导航。棋盘在通知与旋转区之间垂直居中;可操作棋盘在设计审核页。

\ No newline at end of file diff --git a/design/directions/paper/tokens.json b/design/directions/paper/tokens.json new file mode 100644 index 0000000..d93204d --- /dev/null +++ b/design/directions/paper/tokens.json @@ -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 + } + } + ] +} diff --git a/design/directions/toy.html b/design/directions/toy.html index 2354dc6..18e8797 100644 --- a/design/directions/toy.html +++ b/design/directions/toy.html @@ -1 +1 @@ -积木轨道屋 · HEX TRAILS 设计稿
LITTLE PLAYWORKS

积木轨道屋

圆润积木、原色塑胶与压下去有回弹的按钮。棋子像可以拿在手里的小零件。

01 / FIVE WORLDS

同一风格,五种主题

统一的弯曲轨道关卡 · 用实际渲染检查识别度

轻松

积木黄盒

#F4C548
进阶

蓝色装配台

#56AFE0
烧脑

橘红工坊

#EA743E
挑战

绿毯小城

#439A73
每周挑战

太空玩具柜

#244584
02 / DESIGN LANGUAGE

造型与交互规则

  • 同色深浅叠层,形成柔软厚度
  • 红黄蓝绿积木配木色底板
  • 圆角控件、凸起边缘与轻微高光
  • 所有棋子保留独立方向箭头,颜色和表情不改变规则。
  • 离场与碰撞共用游戏动画,切换设计保持路线与时长一致。

这是一套可与其他方案同时保留的候选设计。到审核页勾选保留、记录修改意见;不会替换你的其他选择。

审核与体验这套设计 ↗
03 / COMPONENT STUDY

控件与奖励

撤销
重来
提示

道具底座 64–68px,图标 32px,标签 13px。用独立造型、大小和功能色,与顶部导航按钮区分;纯色平面,无阴影或渐变高光。

FUTURE STATES / 仅设计示意

为次数与广告留出位置

3撤销
重来
AD提示

右上角展示剩余次数,或播放符号 + AD。无角标表示普通状态;数字和获取方式均为样式示例,当前游戏不启用。

★ ★ ★

漂亮,全部过关!

每一次转弯,都找到出口。

继续旅程 →

底部道具独立于顶部导航。棋盘在通知与旋转区之间垂直居中;可操作棋盘在设计审核页。

\ No newline at end of file +积木轨道屋 · 设计稿已迁移

设计稿已整理到独立目录:03 · 积木轨道屋

\ No newline at end of file diff --git a/design/directions/toy.md b/design/directions/toy.md index 08ae6db..cecf264 100644 --- a/design/directions/toy.md +++ b/design/directions/toy.md @@ -1,27 +1,5 @@ -# 积木轨道屋 +# 03 · 积木轨道屋 -圆润积木、原色塑胶与压下去有回弹的按钮。棋子像可以拿在手里的小零件。 +本文件为旧入口。完整、人工维护的 Agent 规范已迁移到 [toy/design.md](toy/design.md)。 -## 设计规则 - -- 同色深浅叠层,形成柔软厚度 -- 红黄蓝绿积木配木色底板 -- 圆角控件、凸起边缘与轻微高光 - -## 五种主题 - -| 难度 | 名称 | 环境色 | -| --- | --- | --- | -| 轻松 | 积木黄盒 | #F4C548 | -| 进阶 | 蓝色装配台 | #56AFE0 | -| 烧脑 | 橘红工坊 | #EA743E | -| 挑战 | 绿毯小城 | #439A73 | -| 每周挑战 | 太空玩具柜 | #244584 | - -## 游戏道具与布局 - -- 撤销、重来、提示使用独立彩色底座,64–68px,图标 32px,标签 13px。顶部导航保持紧凑。道具使用纯色平面,不使用阴影、立体底边或渐变高光。 -- 右上角预留次数或“▶ AD”角标。设计稿展示“撤销 3 次”和“广告获取提示”,仅为样式,不启用真实库存或广告逻辑。 -- 棋盘在通知区与独立旋转区之间垂直居中。旋转区和道具区占据真实布局行,不覆盖棋盘。 - -此方案使用独立的棋子造型、轨道材质和控件样式,共用现有弯曲轨道玩法。审核页支持同时保留多套方案。 +[设计稿](toy/index.html) · [配色数据](toy/tokens.json) · [Agent 接入说明](../README.md) diff --git a/design/directions/toy/design.md b/design/directions/toy/design.md new file mode 100644 index 0000000..be3b164 --- /dev/null +++ b/design/directions/toy/design.md @@ -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 染成玩具配色。 + +- 缩小到手机尺寸后,分层仍清楚且不遮住箭头。 +- 蓝色和太空主题保持木色/浅灰板面,不能五套都用同一个奶油背景。 +- 静态、运动与撤销造型一致;底部道具保持平面。 +- 原生几何可复用绘制命令,原生控件和实机效果尚未验收。 diff --git a/design/directions/toy/index.html b/design/directions/toy/index.html new file mode 100644 index 0000000..f18c749 --- /dev/null +++ b/design/directions/toy/index.html @@ -0,0 +1 @@ +积木轨道屋 · HEX TRAILS 设计稿
LITTLE PLAYWORKS

积木轨道屋

圆润积木、原色塑胶与压下去有回弹的按钮。棋子像可以拿在手里的小零件。

01 / FIVE WORLDS

同一风格,五种主题

统一的弯曲轨道关卡 · 用实际渲染检查识别度

轻松

积木黄盒

#F4C548
进阶

蓝色装配台

#56AFE0
烧脑

橘红工坊

#EA743E
挑战

绿毯小城

#439A73
每周挑战

太空玩具柜

#244584
02 / DESIGN LANGUAGE

造型与交互规则

  • 同色深浅叠层,形成柔软厚度
  • 红黄蓝绿积木配木色底板
  • 圆角控件、凸起边缘与轻微高光
  • 所有棋子保留独立方向箭头,颜色和表情不改变规则。
  • 离场与碰撞共用游戏动画,切换设计保持路线与时长一致。

这是一套可与其他方案同时保留的候选设计。到审核页勾选保留、记录修改意见;不会替换你的其他选择。

审核与体验这套设计 ↗
03 / COMPONENT STUDY

控件与奖励

撤销
重来
提示

道具底座 64–68px,图标 32px,标签 13px。用独立造型、大小和功能色,与顶部导航按钮区分;纯色平面,无阴影或渐变高光。

FUTURE STATES / 仅设计示意

为次数与广告留出位置

3撤销
重来
AD提示

右上角展示剩余次数,或播放符号 + AD。无角标表示普通状态;数字和获取方式均为样式示例,当前游戏不启用。

★ ★ ★

漂亮,全部过关!

每一次转弯,都找到出口。

继续旅程 →

底部道具独立于顶部导航。棋盘在通知与旋转区之间垂直居中;可操作棋盘在设计审核页。

\ No newline at end of file diff --git a/design/directions/toy/tokens.json b/design/directions/toy/tokens.json new file mode 100644 index 0000000..9fe90e8 --- /dev/null +++ b/design/directions/toy/tokens.json @@ -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 + } + } + ] +} diff --git a/design/directions/vivid.html b/design/directions/vivid.html index 3262f5a..5537f57 100644 --- a/design/directions/vivid.html +++ b/design/directions/vivid.html @@ -1 +1 @@ -高彩游乐场 · HEX TRAILS 设计稿
CHROMA CLUB

高彩游乐场

鲜明撞色、粗线条与贴纸式棋子。像一张能玩的彩色海报,明快、有力量。

01 / FIVE WORLDS

同一风格,五种主题

统一的弯曲轨道关卡 · 用实际渲染检查识别度

轻松

青柠派对

#B8F34A
进阶

爆橙热浪

#FF771B
烧脑

钴蓝电场

#3E42EE
挑战

荧粉冲刺

#FF3588
每周挑战

黑光嘉年华

#1B104D
02 / DESIGN LANGUAGE

造型与交互规则

  • 高纯度主色,深色轮廓统一画面
  • 轨道像印刷线,棋子像立起的贴纸
  • 方圆按钮与错位硬阴影
  • 所有棋子保留独立方向箭头,颜色和表情不改变规则。
  • 离场与碰撞共用游戏动画,切换设计保持路线与时长一致。

这是一套可与其他方案同时保留的候选设计。到审核页勾选保留、记录修改意见;不会替换你的其他选择。

审核与体验这套设计 ↗
03 / COMPONENT STUDY

控件与奖励

撤销
重来
提示

道具底座 64–68px,图标 32px,标签 13px。用独立造型、大小和功能色,与顶部导航按钮区分;纯色平面,无阴影或渐变高光。

FUTURE STATES / 仅设计示意

为次数与广告留出位置

3撤销
重来
AD提示

右上角展示剩余次数,或播放符号 + AD。无角标表示普通状态;数字和获取方式均为样式示例,当前游戏不启用。

★ ★ ★

漂亮,全部过关!

每一次转弯,都找到出口。

继续旅程 →

底部道具独立于顶部导航。棋盘在通知与旋转区之间垂直居中;可操作棋盘在设计审核页。

\ No newline at end of file +高彩游乐场 · 设计稿已迁移

设计稿已整理到独立目录:02 · 高彩游乐场

\ No newline at end of file diff --git a/design/directions/vivid.md b/design/directions/vivid.md index 2108bd5..c2fb5b8 100644 --- a/design/directions/vivid.md +++ b/design/directions/vivid.md @@ -1,27 +1,5 @@ -# 高彩游乐场 +# 02 · 高彩游乐场 -鲜明撞色、粗线条与贴纸式棋子。像一张能玩的彩色海报,明快、有力量。 +本文件为旧入口。完整、人工维护的 Agent 规范已迁移到 [vivid/design.md](vivid/design.md)。 -## 设计规则 - -- 高纯度主色,深色轮廓统一画面 -- 轨道像印刷线,棋子像立起的贴纸 -- 方圆按钮与错位硬阴影 - -## 五种主题 - -| 难度 | 名称 | 环境色 | -| --- | --- | --- | -| 轻松 | 青柠派对 | #B8F34A | -| 进阶 | 爆橙热浪 | #FF771B | -| 烧脑 | 钴蓝电场 | #3E42EE | -| 挑战 | 荧粉冲刺 | #FF3588 | -| 每周挑战 | 黑光嘉年华 | #1B104D | - -## 游戏道具与布局 - -- 撤销、重来、提示使用独立彩色底座,64–68px,图标 32px,标签 13px。顶部导航保持紧凑。道具使用纯色平面,不使用阴影、立体底边或渐变高光。 -- 右上角预留次数或“▶ AD”角标。设计稿展示“撤销 3 次”和“广告获取提示”,仅为样式,不启用真实库存或广告逻辑。 -- 棋盘在通知区与独立旋转区之间垂直居中。旋转区和道具区占据真实布局行,不覆盖棋盘。 - -此方案使用独立的棋子造型、轨道材质和控件样式,共用现有弯曲轨道玩法。审核页支持同时保留多套方案。 +[设计稿](vivid/index.html) · [配色数据](vivid/tokens.json) · [Agent 接入说明](../README.md) diff --git a/design/directions/vivid/design.md b/design/directions/vivid/design.md new file mode 100644 index 0000000..87e9ebe --- /dev/null +++ b/design/directions/vivid/design.md @@ -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。 + +- 五主题都应与原版明显不同,保持高纯度用色,棋子与底盘容易分离。 +- 黑光主题检查浅色轨道和深色板面,其他主题检查深线与亮板面的关系。 +- 静态、移动、碰撞时都使用同一贴纸轮廓和箭头。 +- 底部道具无阴影;原生端尚未接入并实机验收此套风格。 diff --git a/design/directions/vivid/index.html b/design/directions/vivid/index.html new file mode 100644 index 0000000..5efe670 --- /dev/null +++ b/design/directions/vivid/index.html @@ -0,0 +1 @@ +高彩游乐场 · HEX TRAILS 设计稿
CHROMA CLUB

高彩游乐场

鲜明撞色、粗线条与贴纸式棋子。像一张能玩的彩色海报,明快、有力量。

01 / FIVE WORLDS

同一风格,五种主题

统一的弯曲轨道关卡 · 用实际渲染检查识别度

轻松

青柠派对

#B8F34A
进阶

爆橙热浪

#FF771B
烧脑

钴蓝电场

#3E42EE
挑战

荧粉冲刺

#FF3588
每周挑战

黑光嘉年华

#1B104D
02 / DESIGN LANGUAGE

造型与交互规则

  • 高纯度主色,深色轮廓统一画面
  • 轨道像印刷线,棋子像立起的贴纸
  • 方圆按钮与错位硬阴影
  • 所有棋子保留独立方向箭头,颜色和表情不改变规则。
  • 离场与碰撞共用游戏动画,切换设计保持路线与时长一致。

这是一套可与其他方案同时保留的候选设计。到审核页勾选保留、记录修改意见;不会替换你的其他选择。

审核与体验这套设计 ↗
03 / COMPONENT STUDY

控件与奖励

撤销
重来
提示

道具底座 64–68px,图标 32px,标签 13px。用独立造型、大小和功能色,与顶部导航按钮区分;纯色平面,无阴影或渐变高光。

FUTURE STATES / 仅设计示意

为次数与广告留出位置

3撤销
重来
AD提示

右上角展示剩余次数,或播放符号 + AD。无角标表示普通状态;数字和获取方式均为样式示例,当前游戏不启用。

★ ★ ★

漂亮,全部过关!

每一次转弯,都找到出口。

继续旅程 →

底部道具独立于顶部导航。棋盘在通知与旋转区之间垂直居中;可操作棋盘在设计审核页。

\ No newline at end of file diff --git a/design/directions/vivid/tokens.json b/design/directions/vivid/tokens.json new file mode 100644 index 0000000..670c46b --- /dev/null +++ b/design/directions/vivid/tokens.json @@ -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 + } + } + ] +} diff --git a/design/examples/render-design.mjs b/design/examples/render-design.mjs new file mode 100644 index 0000000..42f3295 --- /dev/null +++ b/design/examples/render-design.mjs @@ -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)); +} diff --git a/design/index.html b/design/index.html new file mode 100644 index 0000000..5e3dd00 --- /dev/null +++ b/design/index.html @@ -0,0 +1,19 @@ + + +设计总览 · HEX TRAILS + +
HEX TRAILS 设计档案
+
+
ART DIRECTION LIBRARY / 01–05

5 套设计,
放在一起看。

按方案编号整理,每套都有轻松、进阶、烧脑、挑战和每周挑战。
喜欢的可以同时保留,再到审核页逐套记录意见。

最近新增 · 第 05 套轻线纸境提取自玩法变种实验页:暖白纸面、轻盈细轨道、白边纯色六角与细线箭头。把颜色留给棋子,让弯路轻轻浮现。查看完整设计稿 ↗
+
+
01 / COMPARE

同一棋盘,看清不同画法

统一弯曲轨道与七色棋子;切换主题,同时更新全部方案。

查看 25 种主题 ↓
+
+

当前对比:轻松 · 5 套设计

+
03 / 玩具质感积木黄盒
LITTLE PLAYWORKS

积木轨道屋

圆润积木、原色塑胶与压下去有回弹的按钮。棋子像可以拿在手里的小零件。

04 / 卡通动物青草野餐
PAW PARADE

动物弯弯镇

猫咪、小熊、兔子与青蛙沿轨道出游。用表情与耳朵增加性格,方向箭头独立保留。

05 / 实验页衍生雾蓝信纸
PAPER ROUTES

轻线纸境最新

提取自玩法变种实验页:暖白纸面、轻盈细轨道、白边纯色六角与细线箭头。把颜色留给棋子,让弯路轻轻浮现。

+
+
02 / THEME INDEX

5 套设计 × 5 种主题

每个名字对应一份配色。点击主题,直接进入对应方案的试玩与审核。

+
03 / SHARED DETAILS

已经统一的细节

  • 棋盘垂直居中,适配手机、平板、折叠屏和桌面。
  • 通知统一在剩余步数下方;底部三个道具放大,使用无阴影的纯色平面。
  • 奖励星星为发光金色;次数和广告角标仅在设计稿中示意。
  • 第 05 套保留浅色细轨道、细白边和线条箭头,去掉棋盘外围色标与棋子编号。
查看组件与交互规范 ↗
+
+ + + \ No newline at end of file diff --git a/design/manifest.json b/design/manifest.json new file mode 100644 index 0000000..d7aa90b --- /dev/null +++ b/design/manifest.json @@ -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" + } + ] +} diff --git a/design/shared.md b/design/shared.md new file mode 100644 index 0000000..5005c8e --- /dev/null +++ b/design/shared.md @@ -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 不存用户审核意见,也不表示最新方案已被选为默认。文档中的“最新”只表示新增顺序。 + +## 复用现有渲染器 + +浏览器页面已有 `` 时,可在仓库根目录页面的 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) 保留较早的制作简报。它们的“唯一色源”、轨道对比度和动画旧值不能无差别套用到五套设计。新增方案的接入从本目录开始。 diff --git a/docs/art-direction.md b/docs/art-direction.md index f2db139..1d9f587 100644 --- a/docs/art-direction.md +++ b/docs/art-direction.md @@ -1,5 +1,7 @@ # HEX TRAILS 美术方向与制作简报 +> 历史简报,保留 2026-09-11 的核查背景。当前五套主游戏设计的 Agent 入口已整理到 [design/README.md](../design/README.md),规则见 [shared.md](../design/shared.md) 与各套 design.md,数值见对应 tokens.json。下文“唯一数值来源”、数量、时序和设计债描述可能属于旧快照,不应覆盖当前五套方案的独立规则。 + 版本:2026-09-11。本文面向承担本项目**美术设计**工作的会话或成员,说明角色边界、设计系统事实、改色与素材流程、已知设计债,以及三条工作线的入手点。 本文是**工作简报**,不是设计规范本身。数值与规则的唯一来源仍是 `src/design-tokens.js` 与 [Design.md](../Design.md);本文负责把"怎么改、别踩什么、先做什么"讲清楚。 diff --git a/scripts/build-art-directions.mjs b/scripts/build-art-directions.mjs index b0764aa..7a04b0d 100644 --- a/scripts/build-art-directions.mjs +++ b/scripts/build-art-directions.mjs @@ -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 { ICON_ATLAS } from '../src/ui-skin.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}); -for(const d of ART_DIRECTIONS){ - const html=`${d.name} · HEX TRAILS 设计稿
${d.en}

${d.name}

${d.description}

01 / FIVE WORLDS

同一风格,五种主题

统一的弯曲轨道关卡 · 用实际渲染检查识别度

${d.themes.map(t=>`
${t.label}

${t.name}

${t.palette.map(i=>``).join('')}
${t.background}
`).join('')}
02 / DESIGN LANGUAGE

造型与交互规则

    ${d.principles.map(p=>`
  • ${p}
  • `).join('')}
  • 所有棋子保留独立方向箭头,颜色和表情不改变规则。
  • 离场与碰撞共用游戏动画,切换设计保持路线与时长一致。

这是一套可与其他方案同时保留的候选设计。到审核页勾选保留、记录修改意见;不会替换你的其他选择。

审核与体验这套设计 ↗
03 / COMPONENT STUDY

控件与奖励

${itemStudy(name=>itemIcon(name,'../../'+ICON_ATLAS))}
★ ★ ★

漂亮,全部过关!

每一次转弯,都找到出口。

继续旅程 →

底部道具独立于顶部导航。棋盘在通知与旋转区之间垂直居中;可操作棋盘在设计审核页。

`; - await writeFile(new URL(`${d.id}.html`,out),html); - 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`); +const referenceLevel=JSON.parse(await readFile(new URL('../labs/variants/levels.json',import.meta.url),'utf8')).find(l=>l.id==='color-1'); +function referenceStudy(d){ + if(!d.source)return ''; + return `
FROM VARIANTS / 画法提取

线条轻一点,
棋子就清楚一点。

以实验页「双色试航」的初始棋盘作对照:暖白格面、浅蓝灰轨道、陶橙与雾蓝棋子。边界只有一道细白线,箭头用圆头线条画出。

环境 / 棋盘
#EAF0F3 / #FAFBF7
轨道 / 分格
#B2C4CE / #D3DFE3
轨道线宽
格半径 × 0.055
棋子半径 / 白边
格半径 × 0.88 / 0.035
箭头
白色线条 · 圆头 · 无阴影

下方五种主题沿用这套棋盘、轨道与箭头画法。

查看原实验页 ↗
`; } -console.log('Art direction sheets built: 4 designs × 5 themes.'); +for(const d of ART_DIRECTIONS){ + const html=`${d.name} · HEX TRAILS 设计稿
${d.en}

${d.name}

${d.description}

${referenceStudy(d)}
01 / FIVE WORLDS

同一风格,五种主题

统一的弯曲轨道关卡 · 用实际渲染检查识别度

${d.themes.map(t=>`
${t.label}

${t.name}

${t.palette.map(i=>``).join('')}
${t.background}
`).join('')}
02 / DESIGN LANGUAGE

造型与交互规则

    ${d.principles.map(p=>`
  • ${p}
  • `).join('')}
  • 所有棋子保留独立方向箭头,颜色和表情不改变规则。
  • 离场与碰撞共用游戏动画,切换设计保持路线与时长一致。

这是一套可与其他方案同时保留的候选设计。到审核页勾选保留、记录修改意见;不会替换你的其他选择。

审核与体验这套设计 ↗
03 / COMPONENT STUDY

控件与奖励

${itemStudy(name=>itemIcon(name,'../../../'+ICON_ATLAS))}
★ ★ ★

漂亮,全部过关!

每一次转弯,都找到出口。

继续旅程 →

底部道具独立于顶部导航。棋盘在通知与旋转区之间垂直居中;可操作棋盘在设计审核页。

`; + 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),`${d.name} · 设计稿已迁移

设计稿已整理到独立目录:${d.code} · ${d.name}

`); + 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.`); diff --git a/scripts/design-catalog.mjs b/scripts/design-catalog.mjs new file mode 100644 index 0000000..bf7e841 --- /dev/null +++ b/scripts/design-catalog.mjs @@ -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 = ` + +设计总览 · HEX TRAILS + +
HEX TRAILS 设计档案
+
+
ART DIRECTION LIBRARY / 01–${latest.code}

${directions.length} 套设计,
放在一起看。

按方案编号整理,每套都有轻松、进阶、烧脑、挑战和每周挑战。
喜欢的可以同时保留,再到审核页逐套记录意见。

最近新增 · 第 ${latest.code} 套${latest.name}${latest.description}查看完整设计稿 ↗
+
+
01 / COMPARE

同一棋盘,看清不同画法

统一弯曲轨道与七色棋子;切换主题,同时更新全部方案。

查看 ${directions.length * themes.length} 种主题 ↓
+
${themes.map((t,i)=>``).join('')}
+

当前对比:轻松 · ${directions.length} 套设计

+
${directions.map(d=>``).join('')}
+
+
02 / THEME INDEX

${directions.length} 套设计 × ${themes.length} 种主题

每个名字对应一份配色。点击主题,直接进入对应方案的试玩与审核。

${directions.map(d=>``).join('')}
+
03 / SHARED DETAILS

已经统一的细节

  • 棋盘垂直居中,适配手机、平板、折叠屏和桌面。
  • 通知统一在剩余步数下方;底部三个道具放大,使用无阴影的纯色平面。
  • 奖励星星为发光金色;次数和广告角标仅在设计稿中示意。
  • 第 05 套保留浅色细轨道、细白边和线条箭头,去掉棋盘外围色标与棋子编号。
查看组件与交互规范 ↗
+
+ + +`; + await writeFile(new URL('../design/index.html', import.meta.url), html); +} diff --git a/scripts/design-packages.mjs b/scripts/design-packages.mjs new file mode 100644 index 0000000..5dd6262 --- /dev/null +++ b/scripts/design-packages.mjs @@ -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'); +} diff --git a/src/app.js b/src/app.js index 7740722..24b10e8 100644 --- a/src/app.js +++ b/src/app.js @@ -38,6 +38,7 @@ const requestedTheme=LEVEL_THEMES.find(t=>t.id===new URLSearchParams(location.se function applyTheme(score){ 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]; + document.body.dataset.theme=theme.id; document.body.style.setProperty('--color-background',theme.background); document.body.style.setProperty('--scene-ink',theme.ink); $('theme-caption').textContent=activeTheme?`${requestedTheme?'配色预览':theme.label} · ${theme.name}`:'自由配色 · 12 色棋子库'; diff --git a/src/art-directions.js b/src/art-directions.js index f16de50..9e9dfdc 100644 --- a/src/art-directions.js +++ b/src/art-directions.js @@ -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]], ['草地绿、奶油白与动物毛色,不使用糖果蓝紫。','秋叶黄配橡果棕、麦芽金和浅奶油,像一次林间秋游。','溪水青配苔绿与暖棕,保留自然界的冷暖关系。','红土与沙色小径,棕熊、奶油兔和绿蛙像野外探险队。','深森林绿配萤火浅黄,浅毛色角色从夜色中浮现。']), 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 directionTheme(direction,themeId,count=7){ diff --git a/src/design-catalog.js b/src/design-catalog.js new file mode 100644 index 0000000..8fd2759 --- /dev/null +++ b/src/design-catalog.js @@ -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(); diff --git a/src/direction-review.js b/src/direction-review.js index 170bfee..c2afa39 100644 --- a/src/direction-review.js +++ b/src/direction-review.js @@ -13,7 +13,7 @@ export function mountDirectionReview(current,onSelect){ function select(d){ 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))); - detail.innerHTML=`
${d.en} / ${d.code}

${d.name}

${d.description}

查看这套完整设计稿 ↗
`; + detail.innerHTML=`
${d.en} / ${d.code}

${d.name}

${d.description}

${d.source?`

画法来源:${d.source.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 url=new URL(location.href);url.searchParams.set('design',d.id);history.replaceState(null,'',url); onSelect(d); diff --git a/src/direction-sheet.js b/src/direction-sheet.js index 836e7aa..168ce63 100644 --- a/src/direction-sheet.js +++ b/src/direction-sheet.js @@ -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); 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)});}); + 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(); diff --git a/src/renderer.js b/src/renderer.js index a5464bf..99d97b6 100644 --- a/src/renderer.js +++ b/src/renderer.js @@ -10,7 +10,7 @@ import { TILE_FRAMES, TILE_RADIUS, TILE_ASPECT } from './sprites.js'; export const COLORS=PIGMENTS.map(p=>p.color); // 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) { - 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; const textured=p.sprite?.(tile.color,x,y,r)===true; if(!textured){ @@ -25,6 +25,7 @@ export function drawScene(p,level,tiles,view,state={}) { 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 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 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)}; @@ -43,14 +44,13 @@ export function drawScene(p,level,tiles,view,state={}) { 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 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 - // prevents antialiasing seams between touching hexagons. - for(const cell of sorted){const {x,y}=center(cell,view);base.polygon(hexPoints(x,y,size+.2),materials.board);} - // Subtle cell boundaries beneath the rails: retain the hex grid without - // opening background gaps or painting over connected track endpoints. + // Paint cells before rails. Paper keeps the reference's narrow gaps; other + // styles overlap slightly to avoid antialiasing seams. + for(const cell of sorted){const {x,y}=center(cell,view);base.polygon(hexPoints(x,y,paper?size*.97:size+.2),materials.board);} + // Cell boundaries stay beneath the connected track endpoints. for(const cell of sorted){ - const {x,y}=center(cell,view),edge=hexPoints(x,y,size); - base.line([...edge,edge[0]],materials.boardGrid,Math.max(1,size*.024)); + const {x,y}=center(cell,view),edge=hexPoints(x,y,paper?size*.97:size); + base.line([...edge,edge[0]],materials.boardGrid,paper?Math.max(.4,size*.025):Math.max(1,size*.024)); } const rails=[]; 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 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; - 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)}); } } // Pattern studies can emphasize real routes while retaining the other paired // 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,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)); + 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,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.selected) { const cell=level.cells.find(c=>c.q===state.selected.q&&c.r===state.selected.r); diff --git a/src/showcase.js b/src/showcase.js index 602d1f2..a781161 100644 --- a/src/showcase.js +++ b/src/showcase.js @@ -20,7 +20,7 @@ const regularLevel=createStudyLevel(3); let studyLevel=createStudyLevel(5); let colorCount=7; 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();}; function decorateIcons(){ document.querySelectorAll('[data-icon]').forEach(el=>{ @@ -35,8 +35,10 @@ $('pigment-grid').innerHTML=PIGMENTS.map((p,i)=>`
``).join(''); $('quick-theme').onchange=()=>setTheme(direction.themes.find(t=>t.id===$('quick-theme').value)); function setTheme(next){ + const url=new URL(location.href);url.searchParams.set('theme',next.id);history.replaceState(null,'',url); $('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); + document.body.dataset.theme=theme.id; $('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-description').textContent=`${theme.description} 当前展示 ${colorCount} 色扩展搭配。`;$('phone-label').textContent=`${theme.label} · ${theme.name}`; diff --git a/src/token-art.js b/src/token-art.js index 6b6d1d2..d93d8a0 100644 --- a/src/token-art.js +++ b/src/token-art.js @@ -1,8 +1,17 @@ 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 drawStyledToken(p,x,y,r,tile,angle){ const fill=tile.fill,ink='#263A3A'; 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]]; let arrowY=y,arrowScale=.58; if(tile.artStyle==='vivid'){ diff --git a/tests/art-directions.test.js b/tests/art-directions.test.js index 88abf11..441b65c 100644 --- a/tests/art-directions.test.js +++ b/tests/art-directions.test.js @@ -6,8 +6,26 @@ import { drawScene, drawTile, drawExit } from '../src/renderer.js'; import { createStudyLevel } from '../src/study-levels.js'; import { available, trace } from '../src/core.js'; import { layout } from '../src/geometry.js'; -test('each of four art directions has five readable themes and independent token colors',()=>{ - assert.equal(ART_DIRECTIONS.length,4);assert.equal(new Set(ART_DIRECTIONS.map(d=>d.id)).size,4); +test('paper study preserves the variants reference: pale thin rails and outlined hexagons with line arrows',()=>{ + 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].yc.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){ 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);} @@ -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)); 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',()=>{ 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)){ 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); - 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); } }); diff --git a/tests/design-packages.test.js b/tests/design-packages.test.js new file mode 100644 index 0000000..352b231 --- /dev/null +++ b/tests/design-packages.test.js @@ -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/); +});