Jianghu · 功法合成界面设计规范 v1
功法合成 · 界面设计规范
3 本同名同星合 1 本 · 最高 ★5 · 只吃「仓库里还没学」的 · 消耗纸 + 金币
2026-10-11 · v1 · 设计:蔡少芬(UI)· 依据:实读 src/warehouse.js / index.html / server/*.mjs(见 §0)
状态:待评审 → 未动任何源码(本文件是唯一新增物)
§0 阅读记录(这份规范建立在哪些既有代码上)
| 文件 | 读到的部分 | 取到了什么 |
|---|---|---|
| src/warehouse.js | 全文 1923 行:L33–43 筛选常量;L68–131 品质/材料色;L491–952 共用浮层 TipLayer;L502–518 artLevelMulOf;L520–631 tp-* 原语;L657–704 matIconImg/matTipHTML;L706–800 artTipHTML;L1179–1196 visibleArts();L1491–1551 artBarHTML/artCellHTML;L1568–1635 renderArtSide;L1646–1717 学/忘/一键学满与确认框 | 格子/浮层的全部结构与类名;排序键;侧栏动作区;确认框调用先例 |
| index.html | L19–81 :root 变量(--ink-2/.78、--ink-3/.60、--q- 五品阶、--lv- 等级牌);L795–824 9.5px 的教训注释;L1290–1338 仓库窗骨架 CSS;L1342–1464 功法格 CSS(.art-qd/.art-on/.art-job);L1471–1576 .wh-cell 全族;L1579–1788 .wh-tip + tp- 版式;L1797–1817 品质描边/传奇动效/q-chip;L1820–1891 侧栏与详情卡;L2124–2180 顶栏资源条(含 .low 不足态);L2773–2802 .confirm/.cf-;L3180–3209 仓库浮窗 HTML 骨架 | 可直接复用的变量与色板;传奇 box-shadow 动画会吃掉内环;不足态的既有红;浮层 pointer-events:none;右下/左上角标都被占 |
| src/artIcon.js · src/confirm.js | 全文;Q_FALLBACK(#b9b9b9…#e0643c)与 artQualityInfo;confirmDialog 参数与两条铁律注释 | 功法品阶色是另一套;确认框「写会发生什么,不写确定/取消」 |
| server/resources.mjs | L60–94:gold(panel:'top'、icon 💰、img gold)、paper(panel:'mat'、icon 📜、img paper)、RES_ORDER | 金币在顶栏不占材料页;public/assets/res/{paper,gold}.webp 已存在(已 ls 确认) |
| server/arts.mjs · server/artsStore.mjs · server/index.mjs | arts.mjs L505–631(artLevelMul、sumArtStats);artsStore L120–188(list() 返回 uid/artId/at/art;wearerOf 只查 learned);index.mjs L1313–1338(GET /api/arts:items=没学的、worn=学过的、levelMul/expTable 由后端下发) | 合成原料池 = items[];等级倍率「曲线只有一处」的铁律;wearerOf 对未学条目恒 null ⇒ 格子左上角实际是空的 |
必须遵守 全文遵循项目三条既定纪律,本规范不破例:
1 **所有 tips 只拼 tp- 原语*(老大 2026-10-07 定,见 warehouse.js L520–540 注释);
2 格子只留最必要角标,详情走 .wh-tip;
3 曲线(倍率表)由后端下发,前端只兜底(artLevelMulOf 的注释精神,L502–518)。
§1 落地前提:需要后端补的三个东西 新建议
界面上的每个数都要有权威来源 —— 这是本项目吃过最多亏的地方。合成功能上线前,接口至少要补:
| # | 字段/接口 | 形状(建议) | 前端怎么用 |
|---|---|---|---|
| 1 | 功法实例的星级 star | GET /api/arts 的 items[] 与 worn[heroId][] 每条都带 star: 0..5(缺省 0) | 格子星带、tips 印、分组计数、按钮禁用。注意:已学的也要带 —— tips 是仓库/人物面板共用的(TipLayer),学过的那本照样要显示星级与放大后的数值 |
| 2 | 星级倍率表 starMul | GET /api/arts 加 starMul: [1, 1.4, 1.96, 2.744, 3.8416, 5.37824](索引 = 星级,与 levelMul 同款写法) | artStarMulOf(star, table) 优先取表、兜底 1.4**star —— 与 artLevelMulOf 完全同构 |
| 3 | 合成费用 fuseCost | 建议按「品阶 × 目标星级」给:fuseCost: { blue: [{paper, gold}, …6 档], … },或直接每本功法一行 { paper, gold } | 前端只显示不计算。🔴 若前端自己写 纸 = 10 × star,调费用那天就是「界面说 40、实扣 80」 |
🔴 加倍率的铁律(否则重演「浮层 ×1.84、实算 ×5.04」)
加成总倍率 = artLevelMul(lv) × artStarMul(star)。这个乘法必须同时出现在两处、且只有这两处:
server/arts.mjs的sumArtStats()(战斗/面板/战力的唯一口径,现 L601–620);
src/warehouse.js的artTipHTML()里的大数字区(现 L738–748)。
前端仍按老规矩:entry.stats 若有后端权威值优先用它,曲线只是它没到时的兜底。
接口命名建议(后端自己定,前端跟着):POST /api/arts/fuse,体 = { groups: [{ artId, star, times }] }(单本合成就是 times:1 的数组)。由前端把「要合哪几组」说清楚,比 { all:true } 安全:玩家看到的「合 N 组」和后端实际合掉的完全一致,不怕期间掉落/并发把账算歪。
§2 星级在格子上的呈现 —— 「星带」
2.1 位置裁定(把格子逐块排查过)
| 格子里的位置 | 现在被谁占 | 能不能放星级 |
|---|---|---|
| 顶部整条(0–23px) | .wc-name 名字条(12px serif、单行 ellipsis、纸白渐隐底) | ❌ 名字必须完整优先;把星塞进名字会挤掉长名 |
| 左下角 | 装备格是 .wc-ref(+N);功法格空、但底部有 .art-job 职业渐隐条(武器功法必有) | ❌ 会压职业字(同深度) |
| 右下角 | .art-qd 品阶圆点(9px,right/bottom 5px,z3) | ❌ 品阶点的位置最贵;星点挤在旁边会两团混在一起 |
| 左上角 | .art-on「穿」印(实测恒空,见 §9-6) | ❌ 不借它 —— 合印改去右下(§3.2),左侧完全不动 |
| 右下品阶点的上方(bottom 18px) | 空。装备格的 .wc-marks 只在装备上;功法职业条只在底部 16px 内 | ✅ 留给 §3 的「合印」,与品阶点叠成右列 |
| 名字条正下方 24px 起 | 图标上部(折纸封面顶),无任何文字 | ✅ 星带走这里 |
2.2 星带:5 颗固定 7px 星,0 星不渲染
📖 罗汉伏魔神功
📖罗汉伏魔神功
📖罗汉伏魔神功
📖罗汉伏魔神功
左→右:0 星(不渲染星带) / ★1 / ★3 / ★5+传奇(2.5px 金红描边)。
0 星与有星的区别是「有没有这条白底小带」,扫一眼即分 —— 比 5 颗全暗星更干净(那时候每格都挂一条,等于没有信号)。
HTML 骨架(artCellHTML() 里追加)
// star = Math.max(0, Math.min(5, Number(it.star) || 0)) ${star ? <span class="art-stars" title="${star} 星">${Array.from({ length: 5 }, (_, i) => <i class="${i < star ? 'on' : ''}"></i>).join('')}</span> : ''}
CSS(可直接抄)
/ 星带:名字条正下方(名字条高 = 3 + 12×1.2 + 6 ≈ 23.4px,故 24px 起) / .art-cell .art-stars { position: absolute; left: 50%; top: 24px; transform: translateX(-50%); z-index: 3; pointer-events: none; display: flex; gap: 1px; padding: 2px 4px; border-radius: 6px; background: rgba(255,253,247,.90); box-shadow: inset 0 0 0 1px rgba(70,66,55,.10); } .art-cell .art-stars i { width: 7px; height: 7px; flex: 0 0 auto; background: rgba(70,66,55,.22); / 空位:暖灰,同 --tip-line 族 / clip-path: polygon(50% 0,61% 35%,98% 35%,68% 57%,79% 91%,50% 70%,21% 91%,32% 57%,2% 35%,39% 35%); } .art-cell .art-stars i.on { background: var(--star-gold); }
星色:--star-gold: #b8860b 新建议
取项目里已经存在的 DarkGoldenrod(本来就在用:.df-slot.drop-ok、.d-power 描边、大殿建造文案),不是新色相。为什么不直接拿 --q-gold(#d4a441):
| 色 | 值 | 位置 | 与星带的关系 |
|---|---|---|---|
| 宝器品阶 | #e0a83c | 功法格右下圆点(artQualities) | 同屏最近的金 —— 比它更深更棕才能分开 |
| --q-gold | #d4a441 | 装备金品质(描边/名色) | 亮度接近 #e0a83c,会一起糊成一团金 |
| --star-gold | #b8860b | 星级专用(新变量) | 暖棕金,压在纸白底上 ≈ 4.6:1;与上面两金明度差一档,形状又是星 vs 圆点 |
变量建议加进 index.html 的 :root(唯一新增变量,全站星级只引用它):
--star-gold: #b8860b; / 星级(功法合成 2026-10-11)—— 与 --q-gold / 宝器金刻意错开明度 /
🔴 尺寸锁死像素(7px / gap 1px / 胶囊 padding 2×4):不许用 em、%、或随格子缩放的写法 —— 仓库网格 minmax(88px,1fr)(index.html L1340),窄屏最窄就是 88px,星带 5×7+4×1+8 = 47px,居中后左右各余 20.5px,任何缩放写法都会在某档宽度上撞角标。
§3 哪些格子算「可合成」—— 「合印」+ 金内环
3.1 判定(先说数据,再说画面)
// 分组表只需在 render() 前算一次,别在每个格子 O(n²) 里扫 fuseGroups() { const map = new Map(); // key = ${artId}:${star} → items[] for (const it of this.arts) { // ← 唯一原料池:/api/arts 的 items(没学的) const star = Math.max(0, Math.min(5, Number(it.star) || 0)); if (star >= 5) continue; // 5 星满级不再参与 if (this.wearerOf(it.uid)) continue; // 防御:理论上恒 null(items 本来就全是没学的) const k = ${it.artId}:${star}; (map.get(k) || map.set(k, []).get(k)).push(it); } return map; } // 格子上的两问: // dup = 本组总数(含自己);times = Math.floor(dup / 3) = 能合几组 // ready = star < 5 && dup >= 3 → 金印「合」+ 内环 // near = star < 5 && dup === 2 → 灰印「2/3」
🔴 0 星 3 本 与 1 星 3 本同时存在的场景
分组键是 artId + star 两段 —— 「A 的 0 星 3 本」和「A 的 1 星 3 本」是两组,各自都亮「合」印,一键合成处理两组,结果得 1 本 ★1 + 1 本 ★2。星带(§2)让 0 星和 1 星在格子上本来就分得开,不会看成「A 有 6 本」。
3.2 三档视觉
📖回风拂柳剑
📖回风拂柳剑
2/3
📖回风拂柳剑
合
1 本或更少:不标(大部分格子都是 1/3,全标等于没标)。
2/3:右下小灰印(催一下,还没齐)。
≥3:右下金印「合」+ 格子内一圈金线(扫视时一眼抓出所有能合的)。
印上只写「合」不写「3/3」:3/3 与 2/3 只差一个字,扫视时反而像;「合」字独一份。具体几本可合几次,鼠标悬停在 title 与 tips 里看。
⚠️ 这里改过一轮:最早画在左上,实测演示即刻暴露它会压住居中名字的首字(88px 格 + 6 字名,名字左缘 8px 与印的 5–22px 重叠)。现在放到右下、品阶点正上方,与所有既有元素全部错开。
CSS
/ 合印:右下、品阶点正上方(right:5 与 .art-qd 同一条竖线,叠成「右列」) / .art-cell .art-fuse { position: absolute; right: 5px; bottom: 18px; z-index: 3; pointer-events: none; display: flex; align-items: center; justify-content: center; min-width: 17px; height: 17px; padding: 0 4px; border-radius: 5px; font: 700 11px/1 var(--mono); letter-spacing: 0; color: var(--ink-2); background: rgba(255,253,247,.92); box-shadow: inset 0 0 0 1px rgba(70,66,55,.14); } .art-cell .art-fuse.near { color: #8a5b06; } / 2/3 / .art-cell .art-fuse.ready { / 可合(金印,同 .tp-lv 的金印语言) / color: #7a5205; background: rgba(212,164,65,.24); box-shadow: inset 0 0 0 1px rgba(184,134,11,.55); } / 金内环:必须用 ::after 伪元素 —— .wh-cell.q-legend 的 qLegend 动画(index.html L1803–1807) 把 box-shadow 全程占用,任何用 box-shadow 写的内环在传奇格子上都会静默消失 / .art-cell.can-fuse::after { content: ''; position: absolute; inset: 1px; z-index: 2; pointer-events: none; border-radius: 9px; border: 1.5px solid rgba(184,134,11,.62); }
| 决定 | 理由 |
|---|---|
| 不改 border-color 做「可合成」 | 格子的边框色 = 品阶色(.q-*),是全站色语言;一改就「传奇看起来像宝器」 |
| 内环走 ::after 而不是 box-shadow | 见上:.q-legend 的动画吃 box-shadow —— 这是实测现有代码得出的(不是理论) |
| 与选中态叠加不冲突 | .wh-cell.on 是外环(box-shadow: 0 0 0 2px…),内环在里,两层同时在线也读得清 |
| 印在右下、品阶点正上方(right:5 / bottom:18) | 1 左上「穿」印是依赖后端语义的空位,不借;2 右下已有品阶点占着 right:5 这条竖线,合印叠上去形成「右列」= 这本书的徽记列(装备格的 .wc-marks 也是多枚叠角);3 与名字条(顶部整条)、星带(24–35px)、职业条(底部 16px)全不相交;4 2/3 印比「合」宽 11px,右对齐后往左伸,仍在下半区 |
§4 合成入口
4.1 单本合成 → 右侧详情栏,独立「合成」区块 新建议
先排除两个候选:
- 格子上放按钮 否决:88px 格子里已有名字/星带/合印/品阶点/职业条五个元素,再塞按钮必然压图;且一格一按钮会让扫视界面全是控件。
- tips 里放按钮 否决(结构上不可能):
.wh-tip写死pointer-events: none(index.html L1581),它是「看」的浮层,鼠标穿过不接收点击。要放按钮就得改造共用浮层 —— 违反「三处共用一份 tips」的约定,不划算。
落点:.wh-side 的功法侧栏,插在「属性加成」之后、「装给谁」之前。理由:1 现有动作按钮(学习/遗忘/一键学满)都在侧栏 .cd-actions,合成是同类「动作」;2 侧栏 336px 宽,放得下「3 本原料 → 结果 + 消耗 + 按钮」的完整说明;3 「装给谁」是这本书之后的去处,合成是这本书此刻的处理,顺序天然。
HTML 骨架(renderArtSide() 里插入)
<div class="fuse-box ready"> <!-- 可合时加 .ready;不可合时不加 --> <div class="fuse-h">功法合成 <em>3 本同名同星 → 1 本升 1 星</em></div> <div class="fuse-parts"> <span class="fp-ic">${artIconImg(art, '')}</span> <!-- 原料(同一张图,×3) --> <span class="fp-x">×3</span> <span class="fp-arrow">→</span> <span class="fp-ic">${artIconImg(art, '')}</span> <!-- 结果书 --> <b class="fp-star">★${star + 1}</b> </div> <div class="fuse-cost">…见 §5…</div> <div class="fuse-act"> <button class="btn" data-afuse="${uid}" ${can ? '' : 'disabled'}>合 成</button> <span class="fuse-hint lack">${hint}</span> <!-- 禁用原因,见下表 --> </div> <div class="fuse-hint">3 本同名同星合 1 本,最高 ★5;学会的已定型,不能当材料。</div> </div>
按钮四态(文案即原因,和 whRefine 的 title="已达上限 +10" 同一套做法)
| 状态 | 按钮 | hint(按钮右侧小字) | 区块 |
|---|---|---|---|
| 可合 | 「合 成」可点 | (空)或「可合 2 组」 | .fuse-box.ready(金底) |
| 凑不够 3 本 | disabled | 还差 2 本同名同星的功法 | 素底 |
| 纸或金币不足 | disabled | .lack 红字:纸差 12 · 金币差 3,400 | 素底 |
| 已 ★5 | disabled | 已是 5 星(满) | 素底 |
CSS
.fuse-box { margin-top: 12px; padding: 9px 10px 10px; border-radius: 10px; background: rgba(70,66,55,.05); border: 1px solid rgba(70,66,55,.14); } .fuse-box.ready { background: rgba(212,164,65,.10); border-color: rgba(184,134,11,.38); } .fuse-h { display: flex; align-items: baseline; gap: 6px; font: 600 12.5px/1.4 var(--serif); letter-spacing: 1px; color: var(--ink); } .fuse-h em { font-style: normal; margin-left: auto; font-size: 11px; color: var(--ink-2); letter-spacing: 0; } .fuse-parts { display: flex; align-items: center; gap: 6px; margin: 8px 0 7px; } .fuse-parts .fp-ic { width: 24px; height: 24px; border-radius: 6px; overflow: hidden; flex: 0 0 auto; border: 1.5px solid var(--q-white); background: rgba(255,255,255,.75); } .fuse-parts .fp-ic img { width: 100%; height: 100%; object-fit: contain; display: block; } .fuse-parts .fp-x { font: 700 12px/1 var(--mono); color: var(--ink-2); } .fuse-parts .fp-arrow { font-size: 13px; color: var(--ink-2); margin: 0 2px; } .fuse-parts .fp-star { font: 700 12px/1 var(--mono); color: var(--star-gold); } .fuse-act { display: flex; align-items: center; gap: 8px; margin-top: 9px; } .fuse-act .btn { height: 28px; } .fuse-hint { font-size: 11.5px; line-height: 1.5; color: var(--ink-2); } .fuse-hint.lack { color: var(--danger); }
⚠️ 别把「合成」塞进 .cd-actions
那个动作行现在有三个按钮(学习 / 遗忘 / 一键学满,warehouse.js L1627–1631),flex-wrap: wrap。加第四个必然折行,把「学习」挤到第二行 —— 而学习/遗忘是一对,拆开读会误操作。合成坐在自己的 .fuse-box 里,天然隔开「对书」和「对人」两类动作。
4.2 一键合成 → 功法筛选条尾端 新建议
落点:artBarHTML()(warehouse.js L1491–1508)末尾、(N 本)计数之前,插在「只看〇〇能用的」之后。理由:
barHTML()按页签分发(L1301–1303)—— 放这里天然只在功法页出现;放wh-head(顶部操作区)则装备页/材料页也会看到它,得额外做显隐,容易漏。
- 顶栏的「一键分解」是「装备操作区」的成员(鉴定/加工/分解三兄弟),功法页的批处理放在自己的工具条上,两个页签各成体系。
- 与筛选条同排:它作用于「全部未学功法」(不受当前筛选影响)—— 这点必须在按钮 title 里写明,否则玩家开着「只看剑客」点下去,会以为只合了视图里的。
// artBarHTML() 尾部追加: const plan = this.fusePlan(); // [{artId, star, times, paper, gold, afford}] const can = plan.filter((g) => g.afford).reduce((n, g) => n + g.times, 0); const allN = plan.reduce((n, g) => n + g.times, 0); s += <span class="sep"></span>
<button class="btn wh-fuse-all" data-afuseall="1" ${can ? '' : 'disabled'}title="${can ?把仓库里凑够 3 本的功法依次合成(不受上面的筛选影响): fuseAllReason(allN, plan)}">一键合成${can ?<span class="fa-n">${can} 组</span>: ''}</button>;
// 材料不够时按钮旁补一行红字(复用 .fa-lack): // allN > 0 && can === 0 → <span class="fa-lack">${lackText(plan)}</span> // can > 0 && can < allN → <span class="fa-lack">材料只够合 ${can}/${allN} 组</span>
按钮三态
| 状态 | 表现 | title / 旁注 |
|---|---|---|
| 有组可合、材料够 | 「一键合成 [3 组]」,组数金片 | title:把仓库里凑够 3 本的功法依次合成(不受上面的筛选影响) |
| 材料只够一部分 | 仍可点:按组序合到材料不够为止,确认框里写清「本次合 M 组」 | 旁注红字:材料只够合 2/5 组 |
| 材料一组都不够 | disabled | 旁注红字:纸差 120 · 金币差 8,000 |
| 没有可合的组 | disabled,无金片 | title:还没有凑够 3 本的同名同星功法 |
「只够一部分」选择可点而不是禁用:一键合成的直觉是「把能合的都合了」,材料耗尽自然停 —— 比让玩家自己挑哪几组更符合挂机作品的手感。确认框会把「本次合 M 组」写死,点了就是点了。
CSS
.wh-bar .wh-fuse-all .fa-n { margin-left: 5px; font: 700 11px/1 var(--mono); color: #7a5205; background: rgba(212,164,65,.22); border-radius: 4px; padding: 2px 5px; } .wh-bar .wh-fuse-all:disabled .fa-n { opacity: .55; } .wh-bar .fa-lack { font-size: 11.5px; color: var(--danger); letter-spacing: .5px; }
§5 消耗怎么展示 —— 「消耗胶囊」
材料页的语言是折纸图标 + 名字 + 数量(matIconImg,warehouse.js L657–675:webp 图 + 缺图退 emoji);「不足」的语言是顶栏资源条的 .res-item.low(index.html L2168:rgba(178,85,79,.12) 底 + #a4443e 字)。消耗胶囊就是这两者的合体:
<div class="fuse-cost"> <span class="fc-item ${lackPaper ? 'lack' : ''}" title="${lackPaper ? 还差 ${needPaper - hasPaper} : ''}"> ${matIconImg({ img: 'paper', icon: '📜', name: '纸' })}<span>纸</span><b>×150</b> </span> <span class="fc-item ${lackGold ? 'lack' : ''}" title="${lackGold ? 还差 ${needGold - hasGold} : ''}"> ${matIconImg({ img: 'gold', icon: '💰', name: '金币' })}<span>金币</span><b>×200,000</b> </span> </div>
- 图标走
matIconImg()—— 与材料页同一套 webp + emoji 兜底(public/assets/res/paper.webp / gold.webp已确认存在)。
- 数量一律
Number(n).toLocaleString('en-US')(与材料格.wc-count、材料 tips 的「持有 1,234」同一格式);金币即使 7 位数字也是200,000,不会变成 200000 一坨。
- 金币虽然不在材料页(
panel:'top'被matList()过滤掉了,warehouse.js L1408–1411),但用顶栏同款图标 + 名字,玩家一眼对得上顶栏第 5 格。
- 不足项整颗转红(
.lack),title写「还差 N」—— 与顶栏.res-item.low完全同色,不新造红。
.fuse-cost { display: flex; flex-wrap: wrap; gap: 5px 6px; margin-top: 8px; } .fuse-cost .fc-item { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; / 不许把「纸」和「×150」拆行 / padding: 3px 8px 3px 6px; border-radius: 7px; background: rgba(255,255,255,.66); border: 1px solid rgba(70,66,55,.13); font: 12px/1.4 var(--serif); color: var(--ink-2); } .fuse-cost .fc-item b { font: 700 12.5px/1 var(--mono); color: var(--ink); font-variant-numeric: tabular-nums; } .fuse-cost .fc-item > img { width: 14px; height: 14px; object-fit: contain; display: block; } / matIconImg 的 emoji 兜底是绝对定位铺满(.mat-emoji.on 的 inset:0)—— 在胶囊里必须压回普通流 / .fuse-cost .fc-item > .mat-emoji.on { position: static; font-size: 13px; line-height: 1; } .fuse-cost .fc-item.lack { background: var(--danger-bg); border-color: var(--danger-br); color: var(--danger); } .fuse-cost .fc-item.lack b { color: var(--danger); }
§6 功法 tips(artTipHTML)怎么改
6.1 头部:星级印,与 Lv 印并排 只加一枚印,结构不动
现在头部右侧是等级印 .tp-lv(Lv 8/12,金印语言)。星级印放它左边,同一行 —— 两者都是「这本功法现在的状态」,并排读:
// artTipHTML() 的 tpHead.right: right: ${star > 0 ? <span class="tp-star${star === 5 ? ' on5' : ''}">${'★'.repeat(star)}<em>${'☆'.repeat(5 - star)}</em></span> : ''}
<span class="tp-lv">Lv <b>${lv}</b>…</span>
.tp-name 是 display:flex; flex-wrap:wrap; gap:6px(L1623–1626)—— 两枚印是它的直接子项,自动 6px 间距,长名字换行也不会错位。字符 ★/☆(不是 emoji、不是图片):serif 字体实测有字形,且不需要新素材。
.tp-star { font: 700 12px/1 var(--mono); letter-spacing: 1px; color: var(--star-gold); background: rgba(212,164,65,.18); border: 1px solid rgba(184,134,11,.45); border-radius: 6px; padding: 3px 6px; } .tp-star em { font-style: normal; color: rgba(58,56,51,.28); } / 空星:暖灰 / .tp-star.on5 { background: rgba(212,164,65,.34); border-color: rgba(184,134,11,.75); }
6.2 属性数值:× 等级倍率 → 再 × 星级倍率
// 新增(与 artLevelMulOf 完全同构,放在它下面): export function artStarMulOf(star, table = null) { const S = Math.max(0, Math.min(5, Math.round(Number(star) || 0))); const v = Array.isArray(table) ? Number(table[S]) : NaN; if (Number.isFinite(v) && v > 0) return v; return 1.4 ** S; // 兜底:1 / 1.4 / … / 5.37824 }
// artTipHTML() 里: const sMul = artStarMulOf(star, starTable); const mul = artLevelMulOf(lv, levelMul) sMul; // ← 唯一的乘法,和 sumArtStats 同一个数 const scaled = Math.round(v mul * 100) / 100; // (原有行,只换了 mul 的来源)
分节小字的版式(.tp-sec em,就是现在那句「已按 Lv8 放大 ×1.84」)
| lv / star | 文案 | 宽度验算(卡宽 332px,留 26px padding) |
|---|---|---|
| Lv1 · 0 星 | 学会即生效 | 现状,不动 |
| Lv8 · 0 星 | 已按 Lv8 放大 ×1.84 | 现状,不动 |
| Lv1 · 3 星 | ★3 放大 ×2.74 | ≈ 90px ✅ |
| Lv8 · 3 星 | Lv8 ×1.84 · ★3 ×2.74 | ≈ 150px + 标题「属性加成」52px + gap 7 ≈ 209px 🔴 这行字上限 20 个半角宽(约 165px)。再长就会被挤到第二行(.tp-sec 是 flex 不换行,会溢出卡片)—— 所以不写「总倍率 ×12.48」,两段倍率并排已经把来源说清了。 |
6.3 tips 里加不加合成状态?加一枚小 tag,不加按钮/不加成本
- 头部
tags数组追加一枚:{ text: dup >= 3 ? '可合成 3/3' :同款 ${dup}/3, cls: dup >= 3 ? 'warn' : '' }(只在star < 5 && dup >= 2时给)。挂在悬停上「一眼知道能不能动手」,成本最低。
- 不放消耗数字:成本是「做」的信息、且随库存变化(差几本),浮层里容易看到过期语义 —— 消耗只在侧栏合成区块里出现(§5)。
- 不放按钮:浮层
pointer-events:none(§4.1 已述)。
§7 文案定稿(逐字,可直接抄)
7.1 规则说明(玩家要能看懂三件事:3 合 1 / 最高 5 星 / 学会的不能合)
短版(侧栏合成区块底部、一键合成按钮 title):
3 本同名同星合 1 本,最高 ★5;学会的已定型,不能当材料。
长版(材料页说明位 / 图鉴 / 以后的帮助页):
合成规则:3 本同名(同一 artId)同星的功法合成 1 本高 1 星的新书,最高 5 星;每星属性 ×1.4,5 星约 ×5.38。每次合成消耗纸与金币,星级越高消耗越多。已学会的功法不可参与 —— 想留 3 本来合,就先别学那 3 本。
7.2 单本合成确认框(⚠️ 不可逆,必走 confirmDialog,danger: true)
confirmDialog({ title: '合 成 功 法', body: 把 3 本「<b>${esc(art.name)}</b>(★${star})」合成 1 本 <b>★${star + 1}</b>?, list: 消耗:3 本同名功法 + 纸×${paper.toLocaleString('en-US')} + 金币×${gold.toLocaleString('en-US')} —— 不可Revoke, note: 合成后属性按 ×1.4 放大(★${star + 1} 起即 ×${(1.4 ** (star + 1)).toFixed(2)},随等级再放大)。, okText: '确认合成', cancelText: '再想想', danger: true, }); 口径对齐现有先例:学习/遗忘的确认框(warehouse.js L1651–1695)用的就是「会发生什么」而不是「确定/取消」,list 条(暗红底)专门装「永久消失 / 不退还」这类不可逆后果;取消 用「再想想」而不是「取消」,因为这里取消的是有代价的操作。ESC / 点空白 = 取消,由 confirmDialog 自理(capture 阶段拦截,L69–74)。
7.3 一键合成确认框
confirmDialog({ title: '一 键 合 成', body: 把仓库里凑够 3 本的功法按星级从低到高依次合成?, list: 本次合 <b>${groups} 组</b>(共 ${(groups * 3).toLocaleString('en-US')} 本功法)
+ 纸×${paper.toLocaleString('en-US')} + 金币×${gold.toLocaleString('en-US')} —— 原料与材料都不退还,
note: 不可逆:被合掉的功法会永久消失,合成出的书留在仓库。已学会的功法不参与,一个也不会动。, okText: '确认合成', cancelText: '再想想', danger: true, }); 「按星级从低到高」写出来是因为它决定结果(先合 ★1 得 ★2,再合 ★2 —— 同一批里可能连锁升星)。若后端不连锁(一次只升一级、当批不再参与),这句改成「本次合 N 组」即可,请实现者与后端口径对齐后定稿。
7.4 结果提示(wh-foot 底部提示条,摘要不列清单)
this.setNote(合成 ${r.data.groups} 组 · 得 ★${r.data.minStar}~★${r.data.maxStar} 共 ${r.data.got} 本
· 消耗 纸×${r.data.paper.toLocaleString('en-US')} 金币×${r.data.gold.toLocaleString('en-US')});
// 失败(材料中途不足 / 并发抢原料):setNote(r.error, true) —— 后端 error 原文照抄(仓库铁律1)
§8 溢出与像素(逐场景走查)
| 场景 | 算法 | 结论 |
|---|---|---|
| 88px 格 + 5 星 + 传奇 + 6 字名「罗汉伏魔神功」 | 名字 12px serif ×6 字 ≈ 72px ≤ 80px(88 − 左右 padding 8)→ 不省略;星带 47px 居中(x 20.5–67.5,y 24–35);合印(x 55–83,y 53–70)与品阶点(y 74–83)竖列间距 4px,与星带隔了整块图标区 | ✅ 五个元素(名/星带/合印/品阶点/职业条)互不相压 |
| 7 字以上长名 | 既有 text-overflow: ellipsis 单行截断(L1496)——不动 | ✅ 星带不占名字行,截断行为不变 |
| 消耗 纸×150 + 金币×200,000 | 胶囊:14 图标 + 4 + 「金币」≈24 + 4 + 「200,000」≈52 + padding 14 ≈ 112px;两颗 ≈ 200px ≤ 308px(侧栏内容宽) | ✅ 单行放下;再长由 flex-wrap 换行,胶囊内部 nowrap 保证不拆 |
| 百万级金币 1,234,567 | 12.5px mono ×9 字符 ≈ 68px → 胶囊 ≈ 128px,两颗 ≈ 240px | ✅ 仍单行 |
| 一键合成几十组(例:32 组 / 96 本书) | 确认框 list:「本次合 32 组(共 96 本功法)+ 纸×3,200 + 金币×256,000 —— 原料与材料都不退还」≈ 45 字,430px 框内 12.5px ≈ 2 行 | ✅ .cf-list 本来就是块级条,两行不破版 |
| 结果提示 | 「合成 32 组 · 得 ★1~★4 共 32 本 · 消耗 纸×3,200 金币×256,000」≈ 40 字符 ≈ 260px @11.5px(柱宽 1180px) | ✅ 一行;万一换行,.wh-foot 是普通块(min-height:15px),两行只是变胖 17px,不撑破面板 |
| 数值放大后的大数字(★5 + Lv12 = ×12.48) | +1342(5 字符 ≈ 60px @20px mono)在 .tp-num(flex 1 1 82px / max 148px)内 | ✅ 不溢出;百分比最坏 +1234.5% ≈ 96px |
硬约束清单
- 正文(句子类文字)字号 ≥ 11.5px(tp-* 体系现行值);角标数字 11px(对齐
.tp-lv的 11px 印)。9.5px 不碰 —— 那是「按钮里一个字不能删」的专用档(index.html L806–815 的教训),合成界面用不上。
- 星带/合印的几何一律 px 硬编码(7px / 17px / inset 1px),不用 em / % / clamp。
- 数字一律
--mono+font-variant-numeric: tabular-nums+toLocaleString('en-US')(与.wc-count、.res-item .rn同款)。
- 任何新增文字容器默认
flex-wrap可换行,但单个语义单元(胶囊、印)内部white-space: nowrap。
§9 现有界面里会与新功能冲突 / 溢出的地方
| # | 位置 | 问题 | 处置 | ||
|---|---|---|---|---|---|
| 1 | visibleArts() 排序(warehouse.js L1179–1188) | 排序键 = 品阶→系别→职业→名称,没有 star —— 同名不同星的格子按原始顺序混排,「哪几本是一组」看不出来 | 名称比较之后追加 | (xStar - yStar),让同名条目按星级聚堆连排(改动一行,不动其它键) | |
| 2 | artTipHTML 属性区(L738–749) | 现文案「已按 Lv8 放大 ×1.84」只有等级倍率;加星后不改这行 = 老毛病重演(浮层数与实算数不一致) | 按 §6.2 改文案与乘法;sumArtStats 同步乘 artStarMul | ||
| 3 | .wh-tip(index.html L1581) | pointer-events:none → tips 里放不了合成按钮 | 按钮落侧栏(§4.1);tips 只给 tag | ||
| 4 | .wh-cell.q-legend 的 qLegend 动画(L1803–1807) | 动画全程占用 box-shadow → 凡是用 box-shadow 画的「可合成内环」在传奇格子上会静默消失(而传奇恰恰是玩家最想合的) | 内环用 ::after 伪元素(§3.2) | ||
| 5 | 功法格底部(L1359–1365) | .art-job 职业渐隐条占满底边、.art-qd 品阶点占右下 —— 星级/进度若放底部必撞 | 星级走名字下方(§2)、合印走左上(§3) | ||
| 6 | 功法格左上的「穿」印(L1548) | 现状 wearers 对未学条目恒 null(artsStore L186 只查 learned)所以恒空 —— 依赖后端语义,不可借 | 合印放右下(§3.2),左侧彻底不动;若日后 items 混入已学条目,穿印出现也不与任何新元素冲突 | ||
| 7 | 侧栏 .cd-actions(L1627–1631) | 已有学习/遗忘/一键学满三键 + flex-wrap,第四个按钮会折行拆散「学习/遗忘」 | 合成独立 .fuse-box(§4.1) | ||
| 8 | 材料页 matList()(L1408–1411) | 过滤 panel:'top' → 金币不在材料页,消耗文案里的金币不能在材料页找到对应 | 胶囊用顶栏同款金币图标(gold.webp),且名字直写「金币」 | ||
| 9 | 两套功法品阶色并存 | .art-qd 圆点用 artQualities(#e0a83c 金);.wc-name 用装备 --q-*(#d4a441 金)——同屏已有两金 | 星级色再错开一档 = #b8860b(§2.2 表);不要去「统一」这两套(超出本次范围,且会牵动图鉴/掉落) | ||
| 10 | tp-sec em 行宽(L1661–1664) | 332px 卡片、flex 不换行 —— 属性区小字加星后若超 ~20 半角就会溢出 | 文案按 §6.2 定稿(最长 165px) | ||
| 11 | MAT_ICON.gold 前端是 🪙、后端 def 是 💰 | 兜底 emoji 两处不一致(有图时看不见,缺图时就露馅) | 消耗胶囊的兜底走 matIconImg(def) 的 def.icon(后端权威),不用前端 MAT_ICON | ||
| 12 | 一键学满(autoLearnArts,L1697–1717) | 它按战力补齐会消耗仓库里排前面的书 —— 合成过的高星书战力更高,可能被一键学满顺手吃掉(想留着继续合的会被学走) | 不算冲突但必须知会:建议「一键学满」的确认框 note 里加一句「仓库里合过星的书优先被学」或反之排除 —— 口径请老大定 |
§10 我未做 / 不确定的部分(如实)
- 费用曲线没定:纸/金币随星级怎么涨(固定?×2?按品阶分档?)是数值问题,本规范只定展示方式 —— 数字全部来自后端
fuseCost。
- 合成后是「新 uid」还是「原地升级」:我按「新 uid(仓库多一本、原料 3 本消失)」设计的;若后端选择原地改 star,星带渲染逻辑不变,但「选中态/拖拽」需要顺手讨论(uid 不变的原地换星更简单,推荐)。
- 人物面板(
.art-slot7 格)的星级显示:本次只做了 tips。若老大要格子上也有:建议在.as-lv左侧并排一枚★N小印(该格 60px 量级,实测left:3 + Lv印24 + gap2 + 星印18 = 47px放得下)—— 需要动 hall.js,未细化。
- 一键合成是否连锁升星(同一批里 A★1×3 → A★2,再和别的 A★2 凑够 3 再合)—— 确认框文案依赖这个口径(§7.3 注)。
- 我没有跑起来看像素:所有宽度/对比度是静态推算(依据 index.html 现行 CSS 值与字体度量),实现后请用
_shots/那套截图脚本在 88px 最窄列上验收一次。
- 拖拽联动:合成会消耗格子,若玩家正拖着其中一本(
dragstart→ 合成 →dragend),setDragArt(null)的清理路径要确认不报错 —— 属于实现细节,提示一句。
附录 A · 可整段抄走的 CSS(汇总)
/ ============================================================ 功法合成(2026-10-11)—— 唯一新增变量 + 五个组件 ============================================================ / :root { / 星级:项目既有 DarkGoldenrod;与 --q-gold(#d4a441)、宝器圆点(#e0a83c) 错开明度 / --star-gold: #b8860b; }
/ ---- 1 格子星带(名字条正下方;0 星不渲染) ---- / .art-cell .art-stars { position: absolute; left: 50%; top: 24px; transform: translateX(-50%); z-index: 3; pointer-events: none; display: flex; gap: 1px; padding: 2px 4px; border-radius: 6px; background: rgba(255,253,247,.90); box-shadow: inset 0 0 0 1px rgba(70,66,55,.10); } .art-cell .art-stars i { width: 7px; height: 7px; flex: 0 0 auto; background: rgba(70,66,55,.22); clip-path: polygon(50% 0,61% 35%,98% 35%,68% 57%,79% 91%,50% 70%,21% 91%,32% 57%,2% 35%,39% 35%); } .art-cell .art-stars i.on { background: var(--star-gold); }
/ ---- 2 合印(右下、品阶点上方)+ 可合成内环 ---- / .art-cell .art-fuse { position: absolute; right: 5px; bottom: 18px; z-index: 3; pointer-events: none; display: flex; align-items: center; justify-content: center; min-width: 17px; height: 17px; padding: 0 4px; border-radius: 5px; font: 700 11px/1 var(--mono); color: var(--ink-2); background: rgba(255,253,247,.92); box-shadow: inset 0 0 0 1px rgba(70,66,55,.14); } .art-cell .art-fuse.near { color: #8a5b06; } .art-cell .art-fuse.ready { color: #7a5205; background: rgba(212,164,65,.24); box-shadow: inset 0 0 0 1px rgba(184,134,11,.55); } .art-cell.can-fuse::after { content: ''; position: absolute; inset: 1px; z-index: 2; pointer-events: none; border-radius: 9px; border: 1.5px solid rgba(184,134,11,.62); }
/ ---- 3 侧栏合成区块 ---- / .fuse-box { margin-top: 12px; padding: 9px 10px 10px; border-radius: 10px; background: rgba(70,66,55,.05); border: 1px solid rgba(70,66,55,.14); } .fuse-box.ready { background: rgba(212,164,65,.10); border-color: rgba(184,134,11,.38); } .fuse-h { display: flex; align-items: baseline; gap: 6px; font: 600 12.5px/1.4 var(--serif); letter-spacing: 1px; color: var(--ink); } .fuse-h em { font-style: normal; margin-left: auto; font-size: 11px; color: var(--ink-2); letter-spacing: 0; } .fuse-parts { display: flex; align-items: center; gap: 6px; margin: 8px 0 7px; } .fuse-parts .fp-ic { width: 24px; height: 24px; border-radius: 6px; overflow: hidden; flex: 0 0 auto; border: 1.5px solid var(--q-white); background: rgba(255,255,255,.75); } .fuse-parts .fp-ic img { width: 100%; height: 100%; object-fit: contain; display: block; } .fuse-parts .fp-x { font: 700 12px/1 var(--mono); color: var(--ink-2); } .fuse-parts .fp-arrow { font-size: 13px; color: var(--ink-2); margin: 0 2px; } .fuse-parts .fp-star { font: 700 12px/1 var(--mono); color: var(--star-gold); } .fuse-act { display: flex; align-items: center; gap: 8px; margin-top: 9px; } .fuse-act .btn { height: 28px; } .fuse-hint { font-size: 11.5px; line-height: 1.5; color: var(--ink-2); } .fuse-hint.lack { color: #a4443e; }
/ ---- 4 消耗胶囊 ---- / .fuse-cost { display: flex; flex-wrap: wrap; gap: 5px 6px; margin-top: 8px; } .fuse-cost .fc-item { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; padding: 3px 8px 3px 6px; border-radius: 7px; background: rgba(255,255,255,.66); border: 1px solid rgba(70,66,55,.13); font: 12px/1.4 var(--serif); color: var(--ink-2); } .fuse-cost .fc-item b { font: 700 12.5px/1 var(--mono); color: var(--ink); font-variant-numeric: tabular-nums; } .fuse-cost .fc-item > img { width: 14px; height: 14px; object-fit: contain; display: block; } .fuse-cost .fc-item > .mat-emoji.on { position: static; font-size: 13px; line-height: 1; } .fuse-cost .fc-item.lack { background: rgba(178,85,79,.12); border-color: rgba(178,85,79,.40); color: #a4443e; } .fuse-cost .fc-item.lack b { color: #a4443e; }
/ ---- 5 工具条一键合成 ---- / .wh-bar .wh-fuse-all .fa-n { margin-left: 5px; font: 700 11px/1 var(--mono); color: #7a5205; background: rgba(212,164,65,.22); border-radius: 4px; padding: 2px 5px; } .wh-bar .wh-fuse-all:disabled .fa-n { opacity: .55; } .wh-bar .fa-lack { font-size: 11.5px; color: #a4443e; letter-spacing: .5px; }
/ ---- 6 tips 星级印 ---- / .tp-star { font: 700 12px/1 var(--mono); letter-spacing: 1px; color: var(--star-gold); background: rgba(212,164,65,.18); border: 1px solid rgba(184,134,11,.45); border-radius: 6px; padding: 3px 6px; } .tp-star em { font-style: normal; color: rgba(58,56,51,.28); } .tp-star.on5 { background: rgba(212,164,65,.34); border-color: rgba(184,134,11,.75); }
功法合成 · 界面设计规范 v1 · 2026-10-11 · 蔡少芬(UI)
只读设计:本文件不要求改动 src/ / index.html / server/ 的现有行,全部为新增(附录 A 整段可贴 index.html 样式区末尾)。