feat: add Paper Routes and package five design systems

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

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

Validation: 16 focused design, theme and package tests passed; npm run design and static build passed. Confirmed all eight authored documents survive generation unchanged. Browser checks covered mobile, tablet and desktop layouts, theme links and migrated previews. Native engine integration and visual verification remain pending.
This commit is contained in:
cabinw
2026-09-13 13:24:34 +08:00
parent e32a1616be
commit 10cf93a324
52 changed files with 3365 additions and 149 deletions
+20 -6
View File
@@ -1,11 +1,25 @@
import { mkdir, writeFile } from 'node:fs/promises';
import { mkdir, readFile, writeFile } from 'node:fs/promises';
import { itemStudy, itemIcon } from '../src/item-study.js';
import { 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=`<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${d.name} · HEX TRAILS 设计稿</title><link rel="stylesheet" href="../showcase.css"><link rel="stylesheet" href="../art-directions.css"><link rel="stylesheet" href="../game-tools.css"></head><body class="sheet-page" data-design="${d.id}" style="--paper:${d.paper};--accent:${d.accent};--bg:${d.themes[0].background}"><main class="sheet-main"><nav class="sheet-nav"><a href="../../design-showcase.html?design=${d.id}">← 回到设计审核</a><span>HEX TRAILS / ART DIRECTION ${d.code}</span></nav><header class="sheet-hero"><div><span class="eyebrow">${d.en}</span><h1>${d.name}</h1><p>${d.description}</p></div><canvas id="sheet-tokens" class="sheet-token-row" width="720" height="270" aria-label="${d.name}棋子造型设计"></canvas></header><section><div class="section-heading"><div><span class="eyebrow">01 / FIVE WORLDS</span><h2>同一风格,五种主题</h2></div><p>统一的弯曲轨道关卡 · 用实际渲染检查识别度</p></div><div class="sheet-themes">${d.themes.map(t=>`<article class="sheet-theme" style="--bg:${t.background};--ink:${t.ink}"><small>${t.label}</small><h3>${t.name}</h3><canvas data-sheet-theme="${t.id}" width="480" height="520" aria-label="${t.label}主题的弯曲棋盘"></canvas><div class="sheet-swatches">${t.palette.map(i=>`<i style="background:${d.pigments[i].color}" title="${d.pigments[i].color}"></i>`).join('')}</div><small>${t.background}</small></article>`).join('')}</div></section><section class="sheet-components"><div class="sheet-panel"><span class="eyebrow">02 / DESIGN LANGUAGE</span><h2>造型与交互规则</h2><ul>${d.principles.map(p=>`<li>${p}</li>`).join('')}<li>所有棋子保留独立方向箭头,颜色和表情不改变规则。</li><li>离场与碰撞共用游戏动画,切换设计保持路线与时长一致。</li></ul><p class="sheet-caption">这是一套可与其他方案同时保留的候选设计。到审核页勾选保留、记录修改意见;不会替换你的其他选择。</p><a class="sheet-link" href="../../design-showcase.html?design=${d.id}">审核与体验这套设计 ↗</a></div><div class="sheet-panel"><span class="eyebrow">03 / COMPONENT STUDY</span><h2>控件与奖励</h2><div class="component-preview">${itemStudy(name=>itemIcon(name,'../../'+ICON_ATLAS))}<div class="sample-modal"><div class="stars" aria-label="三颗金色星星">★ ★ ★</div><h3>漂亮,全部过关!</h3><p>每一次转弯,都找到出口。</p><a class="sheet-link" href="../../design-showcase.html?design=${d.id}">继续旅程 →</a></div></div><p class="sheet-caption">底部道具独立于顶部导航。棋盘在通知与旋转区之间垂直居中;可操作棋盘在设计审核页。</p></div></section></main><script type="module" src="../../src/direction-sheet.js"></script></body></html>`;
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 `<section class="sheet-reference"><canvas id="sheet-reference-board" width="660" height="660" aria-label="从玩法实验页提取的双色棋盘,保留原有布局与画法"></canvas><div><span class="eyebrow">FROM VARIANTS / 画法提取</span><h2>线条轻一点,<br>棋子就清楚一点。</h2><p>以实验页「双色试航」的初始棋盘作对照:暖白格面、浅蓝灰轨道、陶橙与雾蓝棋子。边界只有一道细白线,箭头用圆头线条画出。</p><dl class="reference-spec"><dt>环境 / 棋盘</dt><dd>#EAF0F3 / #FAFBF7</dd><dt>轨道 / 分格</dt><dd>#B2C4CE / #D3DFE3</dd><dt>轨道线宽</dt><dd>格半径 × 0.055</dd><dt>棋子半径 / 白边</dt><dd>格半径 × 0.88 / 0.035</dd><dt>箭头</dt><dd>白色线条 · 圆头 · 无阴影</dd></dl><p>下方五种主题沿用这套棋盘、轨道与箭头画法。</p><a href="../../../${d.source.href}">查看原实验页 ↗</a></div></section><script type="application/json" id="sheet-reference-data">${JSON.stringify(referenceLevel).replaceAll('<','\\u003c')}</script>`;
}
console.log('Art direction sheets built: 4 designs × 5 themes.');
for(const d of ART_DIRECTIONS){
const html=`<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${d.name} · HEX TRAILS 设计稿</title><link rel="stylesheet" href="../../showcase.css"><link rel="stylesheet" href="../../art-directions.css"><link rel="stylesheet" href="../../game-tools.css"></head><body class="sheet-page" data-design="${d.id}" style="--paper:${d.paper};--accent:${d.accent};--bg:${d.themes[0].background}"><main class="sheet-main"><nav class="sheet-nav"><a href="../../index.html">← 设计总览</a><span>第 ${d.code} 套 / 共 ${ART_DIRECTIONS.length} 套</span><a href="../../../design-showcase.html?design=${d.id}">试玩与审核 ↗</a></nav><nav class="sheet-family-nav" aria-label="切换设计方案">${ART_DIRECTIONS.map(other=>`<a href="../${other.id}/index.html"${other.id===d.id?' aria-current="page"':''}>${other.code} · ${other.name}</a>`).join('')}</nav><div class="sheet-agent-links"><a href="design.md">Agent 设计规范 · design.md ↗</a><a href="tokens.json">配色数据 · JSON ↗</a></div><header class="sheet-hero"><div><span class="eyebrow">${d.en}</span><h1>${d.name}</h1><p>${d.description}</p></div><canvas id="sheet-tokens" class="sheet-token-row" width="720" height="270" aria-label="${d.name}棋子造型设计"></canvas></header>${referenceStudy(d)}<section><div class="section-heading"><div><span class="eyebrow">01 / FIVE WORLDS</span><h2>同一风格,五种主题</h2></div><p>统一的弯曲轨道关卡 · 用实际渲染检查识别度</p></div><div class="sheet-themes">${d.themes.map(t=>`<article class="sheet-theme" style="--bg:${t.background};--ink:${t.ink}"><small>${t.label}</small><h3>${t.name}</h3><canvas data-sheet-theme="${t.id}" width="480" height="520" aria-label="${t.label}主题的弯曲棋盘"></canvas><div class="sheet-swatches">${t.palette.map(i=>`<i style="background:${d.pigments[i].color}" title="${d.pigments[i].color}"></i>`).join('')}</div><small>${t.background}</small></article>`).join('')}</div></section><section class="sheet-components"><div class="sheet-panel"><span class="eyebrow">02 / DESIGN LANGUAGE</span><h2>造型与交互规则</h2><ul>${d.principles.map(p=>`<li>${p}</li>`).join('')}<li>所有棋子保留独立方向箭头,颜色和表情不改变规则。</li><li>离场与碰撞共用游戏动画,切换设计保持路线与时长一致。</li></ul><p class="sheet-caption">这是一套可与其他方案同时保留的候选设计。到审核页勾选保留、记录修改意见;不会替换你的其他选择。</p><a class="sheet-link" href="../../../design-showcase.html?design=${d.id}">审核与体验这套设计 ↗</a></div><div class="sheet-panel"><span class="eyebrow">03 / COMPONENT STUDY</span><h2>控件与奖励</h2><div class="component-preview">${itemStudy(name=>itemIcon(name,'../../../'+ICON_ATLAS))}<div class="sample-modal"><div class="stars" aria-label="三颗金色星星">★ ★ ★</div><h3>漂亮,全部过关!</h3><p>每一次转弯,都找到出口。</p><a class="sheet-link" href="../../../design-showcase.html?design=${d.id}">继续旅程 →</a></div></div><p class="sheet-caption">底部道具独立于顶部导航。棋盘在通知与旋转区之间垂直居中;可操作棋盘在设计审核页。</p></div></section></main><script type="module" src="../../../src/direction-sheet.js"></script></body></html>`;
const packageOut=new URL(`${d.id}/`,out);
await mkdir(packageOut,{recursive:true});
// design.md is authored source; generation must never overwrite it.
await readFile(new URL('design.md',packageOut),'utf8');
await writeFile(new URL('index.html',packageOut),html);
await writeFile(new URL(`${d.id}.html`,out),`<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta http-equiv="refresh" content="0;url=${d.id}/index.html"><title>${d.name} · 设计稿已迁移</title></head><body><p>设计稿已整理到独立目录:<a href="${d.id}/index.html">${d.code} · ${d.name}</a></p></body></html>`);
await writeFile(new URL(`${d.id}.md`,out),`# ${d.code} · ${d.name}\n\n本文件为旧入口。完整、人工维护的 Agent 规范已迁移到 [${d.id}/design.md](${d.id}/design.md)。\n\n[设计稿](${d.id}/index.html) · [配色数据](${d.id}/tokens.json) · [Agent 接入说明](../README.md)\n`);
}
await buildDesignCatalog(ART_DIRECTIONS);
await buildDesignPackages(ART_DIRECTIONS);
console.log(`Art direction catalog and sheets built: ${ART_DIRECTIONS.length} designs × 5 themes.`);
+25
View File
@@ -0,0 +1,25 @@
import { writeFile } from 'node:fs/promises';
export async function buildDesignCatalog(directions) {
const latest = directions.at(-1), themes = directions[0].themes;
const html = `<!doctype html>
<html lang="zh-CN">
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>设计总览 · HEX TRAILS</title><link rel="stylesheet" href="catalog.css"></head>
<body>
<header class="catalog-nav"><a class="brand" href="../index.html">HEX TRAILS <span>设计档案</span></a><nav aria-label="设计文档入口"><a href="README.md">Agent 接入文档 ↗</a><a href="../design-showcase.html">多选与审核 ↗</a></nav></header>
<main>
<section class="catalog-intro"><div><span class="eyebrow">ART DIRECTION LIBRARY / 01–${latest.code}</span><h1>${directions.length} 套设计,<br>放在一起看。</h1><p>按方案编号整理,每套都有轻松、进阶、烧脑、挑战和每周挑战。<br>喜欢的可以同时保留,再到审核页逐套记录意见。</p></div><a class="latest-design" href="directions/${latest.id}/index.html"><span>最近新增 · 第 ${latest.code} 套</span><strong>${latest.name}</strong><small>${latest.description}</small><b>查看完整设计稿 ↗</b></a></section>
<section aria-labelledby="compare-title">
<div class="compare-heading"><div><span class="eyebrow">01 / COMPARE</span><h2 id="compare-title">同一棋盘,看清不同画法</h2><p>统一弯曲轨道与七色棋子;切换主题,同时更新全部方案。</p></div><a href="#theme-map">查看 ${directions.length * themes.length} 种主题 ↓</a></div>
<div class="catalog-theme-picker" role="group" aria-label="对比主题">${themes.map((t,i)=>`<button type="button" data-theme="${t.id}" aria-pressed="${i===0}">${t.label}</button>`).join('')}</div>
<p class="comparison-status" id="comparison-status" aria-live="polite">当前对比:轻松 · ${directions.length} 套设计</p>
<div class="catalog-grid">${directions.map(d=>`<article class="catalog-card" id="${d.id}"><div class="card-preview" style="background:${d.themes[0].background};color:${d.themes[0].ink}"><span class="design-number">${d.code} / ${d.tag}</span><canvas data-catalog-design="${d.id}" width="600" height="600" aria-label="${d.name} · 轻松主题棋盘"></canvas><span class="preview-theme">${d.themes[0].name}</span></div><div class="card-body"><span class="eyebrow">${d.en}</span><h3>${d.name}${d.id===latest.id?'<small>最新</small>':''}</h3><p>${d.description}</p><div class="card-actions"><a href="directions/${d.id}/index.html">完整设计稿 ↗</a><a data-review-design="${d.id}" href="../design-showcase.html?design=${d.id}&theme=lagoon#phone">试玩与审核 →</a></div></div></article>`).join('')}</div>
</section>
<section class="theme-map" id="theme-map" aria-labelledby="themes-title"><span class="eyebrow">02 / THEME INDEX</span><h2 id="themes-title">${directions.length} 套设计 × ${themes.length} 种主题</h2><p>每个名字对应一份配色。点击主题,直接进入对应方案的试玩与审核。</p><div class="theme-matrix">${directions.map(d=>`<article class="theme-family"><h3><a href="directions/${d.id}/index.html"><span>${d.code}</span> ${d.name} ↗</a></h3><div>${d.themes.map(t=>`<a class="theme-entry" href="../design-showcase.html?design=${d.id}&theme=${t.id}#phone"><i style="background:${t.background}" aria-hidden="true"></i><span><small>${t.label}</small><strong>${t.name}</strong></span><b aria-hidden="true">↗</b></a>`).join('')}</div></article>`).join('')}</div></section>
<section class="catalog-notes"><div><span class="eyebrow">03 / SHARED DETAILS</span><h2>已经统一的细节</h2></div><ul><li>棋盘垂直居中,适配手机、平板、折叠屏和桌面。</li><li>通知统一在剩余步数下方;底部三个道具放大,使用无阴影的纯色平面。</li><li>奖励星星为发光金色;次数和广告角标仅在设计稿中示意。</li><li>第 05 套保留浅色细轨道、细白边和线条箭头,去掉棋盘外围色标与棋子编号。</li></ul><a href="../Design.html">查看组件与交互规范 ↗</a></section>
</main>
<footer><span>HEX TRAILS · 设计方案 01–${latest.code}</span><a href="../design-showcase.html">选择方案、记录意见与导出审核 →</a></footer>
<script type="module" src="../src/design-catalog.js"></script>
</body></html>`;
await writeFile(new URL('../design/index.html', import.meta.url), html);
}
+74
View File
@@ -0,0 +1,74 @@
import { writeFile } from 'node:fs/promises';
import { THEME, contrast } from '../src/design-tokens.js';
import { directionTheme } from '../src/art-directions.js';
import { TILE_ATLAS } from '../src/sprites.js';
import { ICON_ATLAS } from '../src/ui-skin.js';
export const paletteCounts = [4, 7, 9, 12];
const materialKeys = ['board', 'boardGrid', 'track', 'trackHalo'];
const sources = {
directions: 'src/art-directions.js',
baseTokens: 'src/design-tokens.js',
renderer: 'src/renderer.js',
tokenDrawing: 'src/token-art.js',
componentStyles: 'design/art-directions.css',
itemStyles: 'design/game-tools.css',
};
export async function buildDesignPackages(directions) {
const manifest = {
schemaVersion: 1,
generated: true,
generatedBy: 'scripts/build-art-directions.mjs',
pathBase: 'repository-root',
entry: 'design/README.md',
agentGuide: 'design/AGENTS.md',
sharedRules: 'design/shared.md',
catalog: 'design/index.html',
example: 'design/examples/render-design.mjs',
defaultDesignId: 'calm',
latestDesignId: directions.at(-1).id,
selectionPolicy: 'multiple-candidates',
themeIds: directions[0].themes.map(t => t.id),
paletteCounts,
sources,
designs: directions.map(d => ({
id: d.id, code: d.code, name: d.name, artStyle: d.style,
status: d.id === 'calm' ? 'existing-default' : 'review-candidate',
directory: `design/directions/${d.id}`,
designDoc: `design/directions/${d.id}/design.md`,
tokens: `design/directions/${d.id}/tokens.json`,
preview: `design/directions/${d.id}/index.html`,
legacyPreview: `design/directions/${d.id}.html`,
})),
};
for (const direction of directions) {
const tokens = {
schemaVersion: 1,
generated: true,
generatedBy: 'scripts/build-art-directions.mjs',
pathBase: 'repository-root',
designId: direction.id,
artStyle: direction.style,
sources,
accent: direction.accent,
documentBackground: direction.paper,
sharedSemanticColors: THEME,
assets: { tileAtlas: direction.id === 'calm' ? TILE_ATLAS : null, uiIconAtlas: ICON_ATLAS },
pigments: direction.pigments.map((pigment, index) => ({ index, ...pigment })),
themes: direction.themes.map(theme => {
const resolved = { ...THEME, ...theme.materials };
const materials = Object.fromEntries(materialKeys.map(key => [key, resolved[key]]));
return {
id: theme.id, label: theme.label, name: theme.name,
background: theme.background, ink: theme.ink, materials,
anchorPalette: theme.palette,
palettes: Object.fromEntries(paletteCounts.map(count => [count, directionTheme(direction, theme.id, count).palette])),
contrast: { sceneText: contrast(theme.ink, theme.background), trackOnBoard: contrast(materials.track, materials.board) },
};
}),
};
await writeFile(new URL(`../design/directions/${direction.id}/tokens.json`, import.meta.url), JSON.stringify(tokens, null, 2) + '\n');
}
await writeFile(new URL('../design/manifest.json', import.meta.url), JSON.stringify(manifest, null, 2) + '\n');
}