dsh-calorie 0.3.40 → 0.3.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.js CHANGED
@@ -1208,141 +1208,358 @@ window.__ModuleLoader__.load({
1208
1208
  }, react.createElement("span", { style: S$2.product }, props.product), react.createElement("span", { style: S$2.purpose }, props.purpose));
1209
1209
  }
1210
1210
  //#endregion
1211
- //#region ../plugin-manager/src/seal-scroll.ts
1212
- /** 印章卷轴 · 纯组件(`title-seal.ts` 的配套件,不认任何一家)。
1213
- *
1214
- * 来源:原型定稿 2026-10-05(记录见 `docs/plugins/plugin-manager/印章原型-定稿.md` 第九节)。
1215
- * 点印后弹出的那块:**外框逐档照抄对应那枚印的材质**(底色+边线都照抄,不许简化成"一圈金属色"),
1216
- * **中间是纯纸**,内容固定三段——进展/状态/计划。
1217
- *
1218
- * 三条形状(逐值照原型锁死):
1219
- * 外框:厚 1.125em(16px 下=18px,这是下限;只给 10px 整张卡会读成"白底加一圈边"),
1220
- * 材质随 `role`×`tier` 九档之一,挂 `roughFrame` 糙边滤镜。
1221
- * 纸:`bg-layer-2` 底+`border-l1` 描边,压在材质上(外框那层在纸下面,只露一圈)。
1222
- * 字:标题居中 1em/字距 .16em;档位字 .66em/字距 .42em;三段标签 .66em/字距 .34em;
1223
- * 正文 .84em/行高 1.9;段间 1px 细线+.94em 上距。
1224
- *
1225
- * 颜色分治(照 `title-seal.ts` 的先例):
1226
- * 印那一份(外框材质、档位字色)**固定色**——纸变印不变,印不管纸面派生;
1227
- * 纸那一份(纸底、纸边、标题、正文、段间线)走 DSH 主题别名。
1228
- *
1229
- * 糙边滤镜两条分开:印是小块,`scale 2.2` 就够;外框有几百像素宽,同一条会被稀释到看不出,
1230
- * 故外框单用 `scale 5` 的 `roughFrame`。滤镜只挂外框那层,纸不挂(否则纸的直边也会被抖)。
1231
- *
1232
- * 本件是纯函数:吃 props 回元素树,不留状态、不取数。手写 `React.createElement`(本包 client 束禁 JSX)。
1233
- */
1234
- /** 糙边滤镜 id(文档作用域,故每页只许挂一次 `SealFilterDefs`)。 */
1235
- const ROUGH_EDGE = "dshLifeSealRoughEdge";
1236
- const ROUGH_FRAME = "dshLifeSealRoughFrame";
1237
- /** 档位话:标题下那一行不再印档位字,直接说档位所处的阶段(渲染期用,顶层可放行)。 */
1238
- const TIER_TEXT = {
1239
- gold: "精雕细琢中",
1240
- silver: "全打通中",
1241
- copper: "基础建设中"
1242
- };
1243
- /** 纸面四色是印自带的(羊皮纸底+ availability 墨色),不跟宿主主题跑——
1244
- * 深色下宿主变量会把纸染黑,故这里写死定稿值;字族仍继承(定稿零字族)。 */
1245
- const PAPER_BG = "#fdfaf2";
1246
- const LABEL_SUB = "#4b4335";
1247
- /** 九档材质(3 角色 × 3 档)。键就是原型里的 `role`/`tier`。 */
1248
- const MATERIALS = {
1211
+ //#region ../plugin-manager/src/seal-palette.ts
1212
+ /** 九档调色板(3 角色 × 3 档)。键就是契约里的 `role`/`tier`。 */
1213
+ const SEAL_PALETTE = {
1249
1214
  skill: {
1250
1215
  copper: {
1251
- frame: {
1252
- background: "linear-gradient(180deg,#8a4a28,#6e3418)",
1253
- border: "3px solid #a5652f",
1254
- boxShadow: "inset 0 0 0 1px #f8f1e2aa, inset 0 2px 0 #ffffff26, inset 0 -3px 6px #0000004d"
1255
- },
1216
+ background: "linear-gradient(180deg,#8a4a28,#6e3418)",
1217
+ border: "3px solid #a5652f",
1256
1218
  accent: "#9a5f2a",
1257
1219
  ruler: "#c08145b3"
1258
1220
  },
1259
1221
  silver: {
1260
- frame: {
1261
- background: "linear-gradient(180deg,#b73124,#8e1f14)",
1262
- border: "3px double #eef3f6",
1263
- boxShadow: "inset 0 0 0 1px #fff, inset 0 2px 0 #ffffff2e, inset 0 -3px 6px #0000004d"
1264
- },
1222
+ background: "linear-gradient(180deg,#b73124,#8e1f14)",
1223
+ border: "3px double #eef3f6",
1265
1224
  accent: "#6f767c",
1266
1225
  ruler: "#9aa0a6b3"
1267
1226
  },
1268
1227
  gold: {
1269
- frame: {
1270
- background: "linear-gradient(180deg,#d34a35,#a32216)",
1271
- border: "3px double #f5d97a",
1272
- boxShadow: "inset 0 0 0 1px #fff8, inset 0 2px 0 #ffffff33, inset 0 -3px 6px #0000004d"
1273
- },
1228
+ background: "linear-gradient(180deg,#d34a35,#a32216)",
1229
+ border: "3px double #f5d97a",
1274
1230
  accent: "#9c7a16",
1275
1231
  ruler: "#d8b338b3"
1276
1232
  }
1277
1233
  },
1278
1234
  help: {
1279
1235
  copper: {
1280
- frame: {
1281
- background: "linear-gradient(180deg,#b06a3a,#7e3f1d)",
1282
- border: "3px solid #a5652f",
1283
- boxShadow: "inset 0 2px 0 #ffffff26, inset 0 -3px 6px #0000004d"
1284
- },
1236
+ background: "linear-gradient(180deg,#b06a3a,#7e3f1d)",
1237
+ border: "3px solid #a5652f",
1285
1238
  accent: "#9a5f2a",
1286
1239
  ruler: "#c08145b3"
1287
1240
  },
1288
1241
  silver: {
1289
- frame: {
1290
- background: "linear-gradient(180deg,#8f979e,#5f666d)",
1291
- border: "3px double #e8eef2",
1292
- boxShadow: "inset 0 2px 0 #ffffff2e, inset 0 -3px 6px #0000004d"
1293
- },
1242
+ background: "linear-gradient(180deg,#8f979e,#5f666d)",
1243
+ border: "3px double #e8eef2",
1294
1244
  accent: "#6f767c",
1295
1245
  ruler: "#9aa0a6b3"
1296
1246
  },
1297
1247
  gold: {
1298
- frame: {
1299
- background: "linear-gradient(180deg,#c63d2a,#a32216)",
1300
- border: "3px double #f5d97a",
1301
- boxShadow: "inset 0 2px 0 #ffffff33, inset 0 -3px 6px #0000004d"
1302
- },
1248
+ background: "linear-gradient(180deg,#c63d2a,#a32216)",
1249
+ border: "3px double #f5d97a",
1303
1250
  accent: "#9c7a16",
1304
1251
  ruler: "#d8b338b3"
1305
1252
  }
1306
1253
  },
1307
1254
  plugin: {
1308
1255
  copper: {
1309
- frame: {
1310
- background: "linear-gradient(180deg,#b4773c,#7e3f1d 60%,#66300f)",
1311
- boxShadow: "inset 0 1px 0 #d99a5e88, inset 0 -2px 5px #0006, 0 0 0 1px #5e2c12, inset 0 2px 0 #ffffff26"
1312
- },
1256
+ background: "linear-gradient(180deg,#b4773c,#7e3f1d 60%,#66300f)",
1313
1257
  accent: "#9a5f2a",
1314
- ruler: "#c08145b3",
1315
- rivet: {
1316
- background: "radial-gradient(circle at 34% 28%,#f0c088,#a5652f 55%,#6a3216)",
1317
- boxShadow: "inset 0 -1px 1px #00000055,0 1px 2px #00000088"
1318
- }
1258
+ ruler: "#c08145b3"
1319
1259
  },
1320
1260
  silver: {
1321
- frame: {
1322
- background: "linear-gradient(180deg,#d8dee3,#a7aeb4 55%,#818990)",
1323
- boxShadow: "inset 0 1px 0 #ffffffdd, inset 0 -2px 5px #0005, 0 0 0 1px #6f767c, inset 0 2px 0 #ffffff33"
1324
- },
1261
+ background: "linear-gradient(180deg,#d8dee3,#a7aeb4 55%,#818990)",
1325
1262
  accent: "#6f767c",
1326
- ruler: "#9aa0a6b3",
1327
- rivet: {
1328
- background: "radial-gradient(circle at 34% 28%,#ffffff,#c3cad0 50%,#7d848b)",
1329
- boxShadow: "inset 0 -1px 1px #00000044,0 1px 2px #00000077"
1330
- }
1263
+ ruler: "#9aa0a6b3"
1331
1264
  },
1332
1265
  gold: {
1333
- frame: {
1334
- background: "linear-gradient(180deg,#f2dc86,#c9a227 55%,#9c7d16)",
1335
- boxShadow: "inset 0 1px 0 #fff8d8dd, inset 0 -2px 5px #0005, 0 0 0 1px #8a6a15"
1336
- },
1266
+ background: "linear-gradient(180deg,#f2dc86,#c9a227 55%,#9c7d16)",
1337
1267
  accent: "#9c7a16",
1338
- ruler: "#d8b338b3",
1339
- rivet: {
1340
- background: "radial-gradient(circle at 34% 28%,#fffbe0,#e3c565 50%,#a5841d)",
1341
- boxShadow: "inset 0 -1px 1px #00000044,0 1px 2px #00000077,0 0 6px #c9a22788"
1342
- }
1268
+ ruler: "#d8b338b3"
1343
1269
  }
1344
1270
  }
1345
1271
  };
1272
+ /** 插件档那两颗铆钉(**两处共用的小件**:印面两颗、卷轴外框四角,必须同形,故住这一层)。 */
1273
+ const SEAL_RIVET = {
1274
+ copper: {
1275
+ background: "radial-gradient(circle at 34% 28%,#f0c088,#a5652f 55%,#6a3216)",
1276
+ boxShadow: "inset 0 -1px 1px #00000055,0 1px 2px #00000088"
1277
+ },
1278
+ silver: {
1279
+ background: "radial-gradient(circle at 34% 28%,#ffffff,#c3cad0 50%,#7d848b)",
1280
+ boxShadow: "inset 0 -1px 1px #00000044,0 1px 2px #00000077"
1281
+ },
1282
+ gold: {
1283
+ background: "radial-gradient(circle at 34% 28%,#fffbe0,#e3c565 50%,#a5841d)",
1284
+ boxShadow: "inset 0 -1px 1px #00000044,0 1px 2px #00000077,0 0 6px #c9a22788"
1285
+ }
1286
+ };
1287
+ /** 档位金属边(铜/银/金):绦带(关闭钮)只借这一条,主体恒朱砂。 */
1288
+ const TIER_EDGE = {
1289
+ gold: "#f5d97a",
1290
+ silver: "#d8dee3",
1291
+ copper: "#a5652f"
1292
+ };
1293
+ /** 档位语三档金属(鎏金/冷银/暖铜):主色+渐变(`backgroundClip:text` 描出金属光泽)。 */
1294
+ const TIER_METAL = {
1295
+ gold: {
1296
+ flat: "#b8912a",
1297
+ gradient: "linear-gradient(180deg,#f6e27a 0%,#d9b53c 45%,#a5811b 100%)"
1298
+ },
1299
+ silver: {
1300
+ flat: "#8b959d",
1301
+ gradient: "linear-gradient(180deg,#f7fafc 0%,#c3ccd3 45%,#8b959d 100%)"
1302
+ },
1303
+ copper: {
1304
+ flat: "#a8663a",
1305
+ gradient: "linear-gradient(180deg,#f0bd8e 0%,#c07b45 45%,#8a4a24 100%)"
1306
+ }
1307
+ };
1308
+ //#endregion
1309
+ //#region ../plugin-manager/src/seal-paper-schemes.ts
1310
+ /** 卷轴纸面字排 + 关闭钮样子 · **定稿件**(2026-10-06 定档,原型选型期结束)。
1311
+ *
1312
+ * 定稿形状(逐值锁死,改动须同步 `test/seal-scroll.test.mjs` 与 `test/seal-bundle-1158.test.mjs`):
1313
+ * 纸面:三段紧凑行内(进展/状态/计划),行首一枚朱砂小方印(进/状/计),标签与正文同行基线对齐;
1314
+ * 末行吃回 0.5em 行距(不然底比顶空一截);饰线上距加宽到 `0.5em 0 0.75em`;纸底与头部沿用原样式。
1315
+ * 档位语:该档金属渐变压进字里(金=鎏金/银=冷银/铜=暖铜),不再一律金。
1316
+ * 关闭钮:右上角斜披的朱砂菱形绦带(正立「收卷」字独立成层),两条飘尾沿菱形两条下边各向底尖收,
1317
+ * 各自朝外 26°(左尾朝左下、右尾朝右下);尾是两层——外层过糙边出毛边,内层纯色芯不受滤镜。
1318
+ *
1319
+ * 为什么单独一件:`seal-scroll.ts` 已接近告警线(外框/纸/浮层/滤镜都住那里),纸面字排与关闭钮住这里。
1320
+ * 本件是纯函数:吃 ctx 回五件,不留状态、不取数。原型期的二十余档方案已按「定稿后只留胜出那一档」删除。
1321
+ */
1322
+ /** 材质底层:**糙边滤镜只挂这一层**——文字住在它外面,所以字是清晰的(照插件章「金属带+印文」的老做法)。 */
1323
+ function bgLayer(style, children) {
1324
+ return react.createElement("span", {
1325
+ "aria-hidden": true,
1326
+ style: {
1327
+ position: "absolute",
1328
+ inset: 0,
1329
+ ...style
1330
+ }
1331
+ }, children ?? null);
1332
+ }
1333
+ /** 清晰字面:压在材质层之上,不参与任何滤镜。 */
1334
+ function crisp(text, style) {
1335
+ return react.createElement("span", { style: {
1336
+ position: "relative",
1337
+ zIndex: 1,
1338
+ ...style
1339
+ } }, text);
1340
+ }
1341
+ /** 档位级两个色(金属边、档位语金属渐变)从九档总表引,本件不再各写一份。 */
1342
+ const edgeOf = (tierKey) => TIER_EDGE[tierKey] ?? TIER_EDGE.gold;
1343
+ const metalOf = (tierKey) => TIER_METAL[tierKey] ?? TIER_METAL.gold;
1344
+ const KAI = "\"Kaiti SC\",\"KaiTi\",\"STKaiti\",serif";
1345
+ /** 关闭钮(定稿:绦带·飘尾)。右上角一枚斜披 45° 的朱砂菱形,两条飘尾分挂两条下边,尾先画、带后画压住尾根。 */
1346
+ function closeLook(tierKey) {
1347
+ const crimson = "linear-gradient(135deg,#c63d2a 8%,#a32216 50%,#7e1a10)";
1348
+ /** 档位金属边(铜/银/金):绦带**只借这一条**——主体恒朱砂,边随档走,跟章「红底+金属边」同构。 */
1349
+ const edge = edgeOf(tierKey);
1350
+ const ribbon = bgLayer({
1351
+ borderRadius: "0.375em",
1352
+ background: crimson,
1353
+ boxShadow: "0 0 0 1px #5c110a59, 0 2px 8px #3d241066, inset 0 0 0 2px " + edge,
1354
+ transform: "rotate(45deg)",
1355
+ filter: "url(#" + SEAL_ROUGH_METAL_ID + ")"
1356
+ }, null);
1357
+ /** 尾色=绦带上亮一档的朱砂(悬在纸上也不发灰、不发黑):两条尾同色。 */
1358
+ const TAIL_FILL = "linear-gradient(180deg,#e8573c 0%,#c33a24 55%,#a52612 100%)";
1359
+ /** 飘尾:屏幕坐标(不随菱形旋转)——左尾往左下、右尾往右下(CSS 正角=尾梢往左)。
1360
+ * 两层:外层过糙边滤镜出毛边,内层纯色芯不受滤镜——尾悬在纸上还是悬在暗底上都是同一色。 */
1361
+ const loose = (key, left, deg, len, top) => react.createElement("span", {
1362
+ key,
1363
+ "aria-hidden": true,
1364
+ style: {
1365
+ position: "absolute",
1366
+ left,
1367
+ top,
1368
+ width: "0.4375em",
1369
+ height: len,
1370
+ transformOrigin: "top center",
1371
+ transform: "rotate(" + deg + "deg)"
1372
+ }
1373
+ }, bgLayer({
1374
+ borderRadius: "0 0 0.1875em 0.1875em",
1375
+ background: TAIL_FILL,
1376
+ boxShadow: "0 0 0 1.5px " + edge + "cc, 0 1px 3px #00000055",
1377
+ filter: "url(#" + SEAL_ROUGH_METAL_ID + ")"
1378
+ }), react.createElement("span", {
1379
+ "aria-hidden": true,
1380
+ style: {
1381
+ position: "absolute",
1382
+ inset: "0.5px",
1383
+ borderRadius: "0 0 0.15625em 0.15625em",
1384
+ background: TAIL_FILL
1385
+ }
1386
+ }));
1387
+ return {
1388
+ label: crisp("收卷", {
1389
+ fontFamily: KAI,
1390
+ fontSize: "0.8125em",
1391
+ letterSpacing: "0.22em",
1392
+ textIndent: "0.22em",
1393
+ color: "#f8f1e2",
1394
+ textShadow: "0 1px 2px #000000aa"
1395
+ }),
1396
+ decor: react.createElement(react.Fragment, null, loose("tL", "calc(50% - 0.8125em)", 26, "2em", "2.8125em"), loose("tR", "calc(50% + 0.4375em)", -26, "2em", "2.8125em"), ribbon),
1397
+ style: {
1398
+ boxSizing: "border-box",
1399
+ padding: 0,
1400
+ position: "absolute",
1401
+ right: "-1.5em",
1402
+ top: "-1.5em",
1403
+ width: "3.25em",
1404
+ height: "3.25em",
1405
+ display: "flex",
1406
+ alignItems: "center",
1407
+ justifyContent: "center",
1408
+ cursor: "pointer",
1409
+ border: "none",
1410
+ background: "transparent"
1411
+ }
1412
+ };
1413
+ }
1414
+ /** 画一条饰线(方向 90/270 决定渐变朝向;定稿的段上距是 0.5em 0 0.75em)。 */
1415
+ function ornament(st, ruler) {
1416
+ const line = (key, dir) => react.createElement("span", {
1417
+ key,
1418
+ style: {
1419
+ ...st.ornLine,
1420
+ background: "linear-gradient(" + dir + "deg,#0000," + ruler + ")"
1421
+ }
1422
+ });
1423
+ return react.createElement("div", { style: {
1424
+ ...st.orn,
1425
+ margin: "0.5em 0 0.75em"
1426
+ } }, line("l", "90"), react.createElement("i", { style: {
1427
+ ...st.ornDot,
1428
+ background: ruler
1429
+ } }), line("r", "270"));
1430
+ }
1431
+ /** 行首那枚朱砂小印(进/状/计):**两层**——材质层过印那条糙边滤镜(印泥压出来的毛边与斑驳),
1432
+ * 字独立成层保持清晰;方块各带一点歪斜(真印章不会次次压得方正)。
1433
+ * 外框仍是 0.7em 字/1.6 行高/左右 0.375em 垫——换的是质感,版面逐值不动。 */
1434
+ function inkMark(name) {
1435
+ const tilt = name === "进展" ? -2.4 : name === "状态" ? 1.6 : -1.2;
1436
+ return react.createElement("span", { style: {
1437
+ position: "relative",
1438
+ flex: "0 0 auto",
1439
+ fontSize: "0.7em",
1440
+ lineHeight: 1.6,
1441
+ padding: "0 0.375em",
1442
+ transform: "rotate(" + tilt + "deg)"
1443
+ } }, react.createElement("span", {
1444
+ "aria-hidden": true,
1445
+ style: {
1446
+ position: "absolute",
1447
+ inset: 0,
1448
+ borderRadius: "0.1875em",
1449
+ background: "#d92b1c",
1450
+ filter: "url(#" + SEAL_ROUGH_INK_ID + ")"
1451
+ }
1452
+ }, react.createElement("span", {
1453
+ "aria-hidden": true,
1454
+ style: {
1455
+ position: "absolute",
1456
+ inset: 0,
1457
+ borderRadius: "inherit",
1458
+ background: "radial-gradient(circle at 72% 76%,#a81a0e33 0%,#0000 58%), radial-gradient(circle at 20% 62%,#f0715a3d 0%,#0000 52%), radial-gradient(circle at 52% 12%,#ee6a5233 0%,#0000 46%)"
1459
+ }
1460
+ })), react.createElement("span", { style: {
1461
+ position: "relative",
1462
+ color: "#fdf7ea",
1463
+ opacity: .95
1464
+ } }, name.slice(0, 1)));
1465
+ }
1466
+ /** 三段:一行紧凑(行首朱砂小方印+标签+正文同一基线)。 */
1467
+ function inlineRows(st, ctx) {
1468
+ const one = (key, name, text, last = false) => react.createElement("div", {
1469
+ key,
1470
+ style: {
1471
+ margin: last ? "0 0 -0.5em" : "0 0 0.5em",
1472
+ display: "flex",
1473
+ alignItems: "baseline",
1474
+ gap: "0.5em"
1475
+ }
1476
+ }, inkMark(name), react.createElement("span", { style: {
1477
+ ...st.label,
1478
+ flex: "0 0 auto",
1479
+ display: "inline",
1480
+ marginBottom: 0,
1481
+ fontSize: "0.8em",
1482
+ color: ctx.accent
1483
+ } }, name + ":"), react.createElement("span", { style: {
1484
+ ...st.text,
1485
+ flex: "1 1 auto",
1486
+ display: "inline",
1487
+ margin: 0,
1488
+ fontSize: "0.9em"
1489
+ } }, text));
1490
+ return react.createElement(react.Fragment, null, one("progress", "进展", ctx.progress), one("status", "状态", ctx.status), one("plan", "计划", ctx.plan, true));
1491
+ }
1492
+ /** 档位语(一行):该档金属渐变压进字里。 */
1493
+ function tierNode(ctx) {
1494
+ const metal = metalOf(ctx.tierKey);
1495
+ return react.createElement("div", { style: {
1496
+ ...ctx.styles.tier,
1497
+ fontFamily: KAI,
1498
+ fontSize: "1.0625em",
1499
+ letterSpacing: "0.34em",
1500
+ textIndent: "0.34em",
1501
+ color: "transparent",
1502
+ backgroundImage: metal.gradient,
1503
+ WebkitBackgroundClip: "text",
1504
+ backgroundClip: "text"
1505
+ } }, ctx.tierText);
1506
+ }
1507
+ /** 组装一件:纸底/头部/档位语/饰线/三段(定稿不覆盖纸底与头部,故两者缺席=用 `seal-scroll.ts` 的原样式)。 */
1508
+ function paperParts(ctx) {
1509
+ return {
1510
+ paper: void 0,
1511
+ head: void 0,
1512
+ tier: tierNode(ctx),
1513
+ orn: ornament(ctx.styles, ctx.ruler),
1514
+ body: inlineRows(ctx.styles, ctx)
1515
+ };
1516
+ }
1517
+ //#endregion
1518
+ //#region ../plugin-manager/src/seal-scroll.ts
1519
+ /** 印章卷轴 · 纯组件(`title-seal.ts` 的配套件,不认任何一家)。
1520
+ *
1521
+ * 来源:原型定稿 2026-10-05(记录见 `docs/plugins/plugin-manager/印章原型-定稿.md` 第九节)。
1522
+ * 点印后弹出的那块:**外框逐档照抄对应那枚印的材质**(底色+边线都照抄,不许简化成"一圈金属色"),
1523
+ * **中间是纯纸**,内容固定三段——进展/状态/计划。
1524
+ *
1525
+ * 三条形状(逐值照原型锁死):
1526
+ * 外框:厚 1.125em(16px 下=18px,这是下限;只给 10px 整张卡会读成"白底加一圈边"),
1527
+ * 材质随 `role`×`tier` 九档之一,挂 `roughFrame` 糙边滤镜。
1528
+ * 纸:`bg-layer-2` 底+`border-l1` 描边,压在材质上(外框那层在纸下面,只露一圈)。
1529
+ * 字:标题居中 1em/字距 .16em;档位字 .66em/字距 .42em;三段标签 .66em/字距 .34em;
1530
+ * 正文 .84em/行高 1.9;段间 1px 细线+.94em 上距。
1531
+ *
1532
+ * 颜色分治(照 `title-seal.ts` 的先例):
1533
+ * 印那一份(外框材质、档位字色)**固定色**——纸变印不变,印不管纸面派生;
1534
+ * 纸那一份(纸底、纸边、标题、正文、段间线)走 DSH 主题别名。
1535
+ *
1536
+ * 糙边滤镜两条分开:印是小块,`scale 2.2` 就够;外框有几百像素宽,同一条会被稀释到看不出,
1537
+ * 故外框单用 `scale 5` 的 `roughFrame`。滤镜只挂外框那层,纸不挂(否则纸的直边也会被抖)。
1538
+ *
1539
+ * 本件是纯函数:吃 props 回元素树,不留状态、不取数。手写 `React.createElement`(本包 client 束禁 JSX)。
1540
+ */
1541
+ /** 糙边滤镜 id(文档作用域,故每页只许挂一次 `SealFilterDefs`)。 */
1542
+ const ROUGH_EDGE = "dshLifeSealRoughEdge";
1543
+ const ROUGH_FRAME = "dshLifeSealRoughFrame";
1544
+ /** 中等件(轴杆/纸筒/绦带这类几十像素宽的件)那条糙边:2.2 太细、5 会抖过头。 */
1545
+ const ROUGH_METAL = "dshLifeSealRoughMetal";
1546
+ /** 印泥那条(行首那几枚小印):同 2.2 的抖动,末尾再加一点高斯模糊——印泥压在纸上边缘会洇开,
1547
+ * 光有抖动边只会读成「毛边贴纸」。字住在滤镜之外,所以糊的只有泥、不是字。 */
1548
+ const ROUGH_INK = "dshLifeSealInkEdge";
1549
+ /** 中等件那条糙边 id(关闭钮引用;定义只此一处)。 */
1550
+ const SEAL_ROUGH_METAL_ID = ROUGH_METAL;
1551
+ /** 印泥那条糙边 id(纸面行首小印引用;定义只此一处)。 */
1552
+ const SEAL_ROUGH_INK_ID = ROUGH_INK;
1553
+ /** 档位话:标题下那一行不再印档位字,直接说档位所处的阶段(渲染期用,顶层可放行)。 */
1554
+ const TIER_TEXT = {
1555
+ gold: "精雕细琢中",
1556
+ silver: "全打通中",
1557
+ copper: "基础建设中"
1558
+ };
1559
+ /** 纸面四色是印自带的(羊皮纸底+ availability 墨色),不跟宿主主题跑——
1560
+ * 深色下宿主变量会把纸染黑,故这里写死定稿值;字族仍继承(定稿零字族)。 */
1561
+ const PAPER_BG = "#fdfaf2";
1562
+ const PAPER_EDGE = "#e6d9bd";
1346
1563
  const S$1 = {
1347
1564
  /** 外框(`.frame`):绝对铺满,挂糙边滤镜;纸压在它上面,只露一圈。 */
1348
1565
  frame: {
@@ -1377,7 +1594,7 @@ window.__ModuleLoader__.load({
1377
1594
  },
1378
1595
  inner: {
1379
1596
  position: "relative",
1380
- padding: "1.75em 2em 1.875em"
1597
+ padding: "1.75em 2em"
1381
1598
  },
1382
1599
  head: {
1383
1600
  textAlign: "center",
@@ -1427,7 +1644,7 @@ window.__ModuleLoader__.load({
1427
1644
  fontSize: "0.84em",
1428
1645
  lineHeight: 1.9,
1429
1646
  letterSpacing: "0.02em",
1430
- color: LABEL_SUB
1647
+ color: "#4b4335"
1431
1648
  },
1432
1649
  /** 卷轴浮层:卡片内绝对定位(卡片 `S.card` 是 relative 定位祖先),上沿悬在三枚章之下、左右各留半 em——
1433
1650
  * 不是 modal:无遮罩、不锁滚动,点章切换、点「关闭」收起。 */
@@ -1441,11 +1658,12 @@ window.__ModuleLoader__.load({
1441
1658
  justifyContent: "center",
1442
1659
  pointerEvents: "auto"
1443
1660
  },
1444
- /** 浮层内框:宽按 em 给(16px 下≈25em),窄卡上收满可用宽,永不捅破卡片;
1661
+ /** 浮层内框:**宽随内容**(`max-content`,正好包住最长那一行;不是固定 25em 那块大纸)。
1662
+ * `maxWidth:100%` 是窄卡的兜底(收满可用宽、永不捅破卡片),此时长行换行、高度自己长。
1445
1663
  * 挂卷轴展开动画(从上而下舒卷,240ms;纯声明式,无 hook,纯函数可直测)。 */
1446
1664
  popInner: {
1447
1665
  position: "relative",
1448
- width: "25em",
1666
+ width: "max-content",
1449
1667
  maxWidth: "100%",
1450
1668
  transformOrigin: "50% 0",
1451
1669
  animation: "dshLifeSealUnroll 240ms ease-out"
@@ -1464,16 +1682,47 @@ window.__ModuleLoader__.load({
1464
1682
  cursor: "pointer"
1465
1683
  }
1466
1684
  };
1685
+ /** 外框专有的内阴影(随档;底色与边线不在这张表里,从调色板来)。
1686
+ * 外框比印面大得多,同一圈金色在小章上够、在几百像素的框上会被稀释,故这一层是外框自己的排法。 */
1687
+ const FRAME_INSET = {
1688
+ skill: {
1689
+ copper: "inset 0 0 0 1px #f8f1e2aa, inset 0 2px 0 #ffffff26, inset 0 -3px 6px #0000004d",
1690
+ silver: "inset 0 0 0 1px #fff, inset 0 2px 0 #ffffff2e, inset 0 -3px 6px #0000004d",
1691
+ gold: "inset 0 0 0 1px #fff8, inset 0 2px 0 #ffffff33, inset 0 -3px 6px #0000004d"
1692
+ },
1693
+ help: {
1694
+ copper: "inset 0 2px 0 #ffffff26, inset 0 -3px 6px #0000004d",
1695
+ silver: "inset 0 2px 0 #ffffff2e, inset 0 -3px 6px #0000004d",
1696
+ gold: "inset 0 2px 0 #ffffff33, inset 0 -3px 6px #0000004d"
1697
+ },
1698
+ plugin: {
1699
+ copper: "inset 0 1px 0 #d99a5e88, inset 0 -2px 5px #0006, 0 0 0 1px #5e2c12, inset 0 2px 0 #ffffff26",
1700
+ silver: "inset 0 1px 0 #ffffffdd, inset 0 -2px 5px #0005, 0 0 0 1px #6f767c, inset 0 2px 0 #ffffff33",
1701
+ gold: "inset 0 1px 0 #fff8d8dd, inset 0 -2px 5px #0005, 0 0 0 1px #8a6a15"
1702
+ }
1703
+ };
1704
+ /** 取一档材质:底色与边线从调色板来(与印共用一份取值),外框专有的内阴影与铆钉在这里合成。 */
1467
1705
  function materialOf(role, tier) {
1468
- const found = MATERIALS[role]?.[tier];
1469
- if (!found) throw new Error("未知的印档:" + role + "/" + tier);
1470
- return found;
1706
+ const palette = SEAL_PALETTE[role]?.[tier];
1707
+ if (palette === void 0) throw new Error("未知的印档:" + role + "/" + tier);
1708
+ return {
1709
+ frame: {
1710
+ background: palette.background,
1711
+ ...palette.border === void 0 ? {} : { border: palette.border },
1712
+ boxShadow: FRAME_INSET[role][tier]
1713
+ },
1714
+ accent: palette.accent,
1715
+ ruler: palette.ruler,
1716
+ rivet: role === "plugin" ? SEAL_RIVET[tier] : void 0
1717
+ };
1471
1718
  }
1472
1719
  /** 糙边滤镜定义:两条分开(印用 edge,卷轴外框用 frame),每页只挂一次。
1473
1720
  *
1474
- * 挂在文档任意位置都行(`width/height=0` 不占位),同页重复挂会撞 id。 */
1721
+ * 挂在文档任意位置都行(`width/height=0` 不占位),同页重复挂会撞 id。
1722
+ * 印章交互(悬停上浮高亮/按压回缩/焦点环/级别 tooltip)也住这里:scoped 类名,纯 CSS、无 hook。
1723
+ * 悬停高亮须把糙边 url 原样带上(filter 单属性,另写会把糙边冲掉)。 */
1475
1724
  function SealFilterDefs() {
1476
- return react.createElement("svg", {
1725
+ return react.createElement(react.Fragment, null, react.createElement("style", null, ".dshLifeSealBtn{position:relative;transition:transform 160ms ease-out,filter 160ms ease-out}.dshLifeSealBtn:hover{transform:translateY(-2px);filter:url(#dshLifeSealRoughEdge) brightness(1.12)}.dshLifeSealBtn:active{transform:translateY(0) scale(.96)}.dshLifeSealBtn:focus-visible{outline:2px solid #f5d97a;outline-offset:2px}.dshLifeSealBtn::after{content:attr(data-tip);position:absolute;bottom:calc(100% + 8px);left:50%;transform:translateX(-50%);background:#241f19ee;color:#f8f1e2;font-size:.72em;letter-spacing:.1em;padding:.4em .9em;border-radius:.5em;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity 150ms ease;z-index:30}.dshLifeSealBtn:hover::after,.dshLifeSealBtn:focus-visible::after{opacity:1}"), react.createElement("svg", {
1477
1726
  width: 0,
1478
1727
  height: 0,
1479
1728
  "aria-hidden": true,
@@ -1497,6 +1746,21 @@ window.__ModuleLoader__.load({
1497
1746
  y: "-6%",
1498
1747
  width: "112%",
1499
1748
  height: "112%"
1749
+ }, react.createElement("feTurbulence", {
1750
+ baseFrequency: .6,
1751
+ numOctaves: 3,
1752
+ result: "n"
1753
+ }), react.createElement("feDisplacementMap", {
1754
+ in: "SourceGraphic",
1755
+ in2: "n",
1756
+ scale: 6.5
1757
+ })), react.createElement("filter", {
1758
+ id: ROUGH_METAL,
1759
+ key: ROUGH_METAL,
1760
+ x: "-8%",
1761
+ y: "-14%",
1762
+ width: "116%",
1763
+ height: "128%"
1500
1764
  }, react.createElement("feTurbulence", {
1501
1765
  baseFrequency: .9,
1502
1766
  numOctaves: 2,
@@ -1504,8 +1768,43 @@ window.__ModuleLoader__.load({
1504
1768
  }), react.createElement("feDisplacementMap", {
1505
1769
  in: "SourceGraphic",
1506
1770
  in2: "n",
1507
- scale: 5
1508
- })));
1771
+ scale: 3.2
1772
+ })), react.createElement("filter", {
1773
+ id: ROUGH_INK,
1774
+ key: ROUGH_INK,
1775
+ x: "-25%",
1776
+ y: "-25%",
1777
+ width: "150%",
1778
+ height: "150%"
1779
+ }, react.createElement("feTurbulence", {
1780
+ type: "fractalNoise",
1781
+ baseFrequency: .9,
1782
+ numOctaves: 2,
1783
+ result: "n"
1784
+ }), react.createElement("feDisplacementMap", {
1785
+ in: "SourceGraphic",
1786
+ in2: "n",
1787
+ scale: 2.2,
1788
+ result: "d"
1789
+ }), react.createElement("feGaussianBlur", {
1790
+ in: "d",
1791
+ stdDeviation: .3,
1792
+ result: "b"
1793
+ }), react.createElement("feTurbulence", {
1794
+ type: "fractalNoise",
1795
+ baseFrequency: .75,
1796
+ numOctaves: 3,
1797
+ result: "g"
1798
+ }), react.createElement("feColorMatrix", {
1799
+ in: "g",
1800
+ type: "matrix",
1801
+ values: "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .7 .45 0 0 .35",
1802
+ result: "mask"
1803
+ }), react.createElement("feComposite", {
1804
+ in: "b",
1805
+ in2: "mask",
1806
+ operator: "in"
1807
+ }))));
1509
1808
  }
1510
1809
  /** 一卷:外框材质 + 纯纸 + 进展/状态/计划三段。 */
1511
1810
  function SealScroll(props) {
@@ -1548,38 +1847,38 @@ window.__ModuleLoader__.load({
1548
1847
  }
1549
1848
  })
1550
1849
  ] : [];
1551
- const line = (key, dir) => react.createElement("span", {
1552
- key,
1553
- style: {
1554
- ...S$1.ornLine,
1555
- background: "linear-gradient(" + dir + "deg,#0000," + material.ruler + ")"
1850
+ /** 纸面字排:定稿件直出五件。 */
1851
+ const parts = paperParts({
1852
+ tierKey: props.tier,
1853
+ tierText: TIER_TEXT[props.tier],
1854
+ progress: props.progress,
1855
+ status: props.status,
1856
+ plan: props.plan,
1857
+ accent: material.accent,
1858
+ ruler: material.ruler,
1859
+ paperEdge: PAPER_EDGE,
1860
+ styles: {
1861
+ head: S$1.head,
1862
+ paper: S$1.paper,
1863
+ tier: S$1.tier,
1864
+ orn: S$1.orn,
1865
+ ornLine: S$1.ornLine,
1866
+ ornDot: S$1.ornDot,
1867
+ block: S$1.block,
1868
+ blockDivided: S$1.blockDivided,
1869
+ blockLast: S$1.blockLast,
1870
+ label: S$1.label,
1871
+ text: S$1.text
1556
1872
  }
1557
1873
  });
1558
- const block = (key, name, text, divided, last = false) => react.createElement("div", {
1559
- key,
1560
- style: last ? divided ? {
1561
- ...S$1.blockLast,
1562
- paddingTop: "0.9375em",
1563
- borderTop: "1px solid #e6d9bd"
1564
- } : S$1.blockLast : divided ? S$1.blockDivided : S$1.block
1565
- }, react.createElement("div", { style: {
1566
- ...S$1.label,
1567
- color: material.accent
1568
- } }, name), react.createElement("p", { style: S$1.text }, text));
1569
1874
  return react.createElement("div", { style: S$1.scroll }, react.createElement("div", { style: {
1570
1875
  ...S$1.frame,
1571
1876
  ...material.frame
1572
- } }, ...frameChildren), react.createElement("div", { style: S$1.paper }, react.createElement("div", { style: S$1.inner }, react.createElement("div", { style: S$1.head }, react.createElement(SealStamp, {
1877
+ } }, ...frameChildren), react.createElement("div", { style: parts.paper === void 0 ? S$1.paper : parts.paper }, react.createElement("div", { style: S$1.inner }, react.createElement("div", { style: parts.head === void 0 ? S$1.head : parts.head }, react.createElement(SealStamp, {
1573
1878
  role: props.role,
1574
1879
  tier: props.tier,
1575
1880
  label: props.title
1576
- }), react.createElement("div", { style: {
1577
- ...S$1.tier,
1578
- color: material.accent
1579
- } }, TIER_TEXT[props.tier])), react.createElement("div", { style: S$1.orn }, line("l", "90"), react.createElement("i", { style: {
1580
- ...S$1.ornDot,
1581
- background: material.accent
1582
- } }), line("r", "270")), block("progress", "进展", props.progress, false), block("status", "状态", props.status, true), block("plan", "计划", props.plan, true, true))));
1881
+ }), parts.tier), parts.orn, parts.body)));
1583
1882
  }
1584
1883
  /** 卷轴浮层:popover + 点击别处关闭 + 卷轴展开动画 + 关闭。`open` 为假时不渲染(纯函数,不收自己的状态)。 */
1585
1884
  function SealScrollDialog(props) {
@@ -1602,16 +1901,27 @@ window.__ModuleLoader__.load({
1602
1901
  "aria-hidden": true,
1603
1902
  onClick: props.onClose
1604
1903
  }), react.createElement("div", {
1605
- style: S$1.dialog,
1904
+ style: props.anchor === void 0 ? S$1.dialog : {
1905
+ ...S$1.dialog,
1906
+ ...props.anchor
1907
+ },
1606
1908
  role: "dialog",
1607
1909
  "aria-modal": false,
1608
1910
  "aria-label": props.title
1609
- }, react.createElement("style", null, "@keyframes dshLifeSealUnroll{from{opacity:0;transform:translateY(-10px) scaleY(.7)}to{opacity:1;transform:none}}"), react.createElement("div", { style: S$1.popInner }, react.createElement("button", {
1610
- type: "button",
1611
- style: S$1.close,
1612
- "data-ilife-press": "seal-close",
1613
- onClick: props.onClose
1614
- }, "关闭"), react.createElement(SealScroll, scroll))));
1911
+ }, react.createElement("style", null, "@keyframes dshLifeSealUnroll{from{opacity:0;transform:translateY(-10px) scaleY(.7)}to{opacity:1;transform:none}}"), react.createElement("div", { style: S$1.popInner }, (() => {
1912
+ const look = closeLook(props.tier);
1913
+ return react.createElement("button", {
1914
+ type: "button",
1915
+ className: "dshLifeSealBtn",
1916
+ style: {
1917
+ zIndex: 20,
1918
+ ...look.style
1919
+ },
1920
+ "data-ilife-press": "seal-close",
1921
+ "data-ilife-close": "tie",
1922
+ onClick: props.onClose
1923
+ }, look.decor ?? null, look.label);
1924
+ })(), react.createElement(SealScroll, scroll))));
1615
1925
  }
1616
1926
  //#endregion
1617
1927
  //#region ../plugin-manager/src/seal-stamp.ts
@@ -1653,88 +1963,64 @@ window.__ModuleLoader__.load({
1653
1963
  color: "#f8f1e2"
1654
1964
  }
1655
1965
  };
1656
- /** 九档印面(底色加边线都照抄对应那枚印,不许简化)。 */
1657
- const FACE = {
1966
+ /** 印面专有的内圈高光与外发光(随档;底色与边线不在这张表里,从调色板来)。 */
1967
+ const FACE_EXTRA = {
1658
1968
  skill: {
1659
1969
  copper: {
1660
- background: "linear-gradient(180deg,#8a4a28,#6e3418)",
1661
1970
  color: "#f8f1e2",
1662
- border: "3px solid #a5652f",
1663
1971
  boxShadow: "inset 0 0 0 1px #f8f1e2aa"
1664
1972
  },
1665
1973
  silver: {
1666
- background: "linear-gradient(180deg,#b73124,#8e1f14)",
1667
1974
  color: "#f8f1e2",
1668
- border: "3px double #eef3f6",
1669
1975
  boxShadow: "inset 0 0 0 1px #fff"
1670
1976
  },
1671
1977
  gold: {
1672
- background: "linear-gradient(180deg,#d34a35,#a32216)",
1673
1978
  color: "#fff8e0",
1674
- border: "3px double #f5d97a",
1675
1979
  boxShadow: "inset 0 0 0 1px #fff8,0 4px 16px #c9a22755"
1676
1980
  }
1677
1981
  },
1678
1982
  help: {
1679
- copper: {
1680
- background: "linear-gradient(180deg,#b06a3a,#7e3f1d)",
1681
- border: "3px solid #a5652f"
1682
- },
1683
- silver: {
1684
- background: "linear-gradient(180deg,#8f979e,#5f666d)",
1685
- border: "3px double #e8eef2"
1686
- },
1687
- gold: {
1688
- background: "linear-gradient(180deg,#c63d2a,#a32216)",
1689
- border: "3px double #f5d97a"
1690
- }
1983
+ copper: {},
1984
+ silver: {},
1985
+ gold: {}
1691
1986
  },
1692
1987
  plugin: {
1693
- copper: { background: "linear-gradient(180deg,#b4773c,#7e3f1d 60%,#66300f)" },
1694
- silver: { background: "linear-gradient(180deg,#d8dee3,#a7aeb4 55%,#818990)" },
1695
- gold: { background: "linear-gradient(180deg,#f2dc86,#c9a227 55%,#9c7d16)" }
1988
+ copper: {},
1989
+ silver: {},
1990
+ gold: {}
1696
1991
  }
1697
1992
  };
1698
1993
  /** 插件整面金属带的描边(随档)。 */
1699
1994
  const BAND_EDGE = {
1700
- copper: { boxShadow: "inset 0 1px 0 #d99a5e88,inset 0 -2px 5px #0006,0 0 0 1px #5e2c12" },
1701
- silver: { boxShadow: "inset 0 1px 0 #ffffffdd,inset 0 -2px 5px #0005,0 0 0 1px #6f767c" },
1702
- gold: { boxShadow: "inset 0 1px 0 #fff8d8dd,inset 0 -2px 5px #0005,0 0 0 1px #8a6a15,0 3px 14px #c9a22766" }
1703
- };
1704
- /** 插件左右铆钉(随档,两颗)。 */
1705
- const RIVET = {
1706
- copper: {
1707
- background: "radial-gradient(circle at 34% 28%,#f0c088,#a5652f 55%,#6a3216)",
1708
- boxShadow: "inset 0 -1px 1px #00000055,0 1px 2px #00000088"
1709
- },
1710
- silver: {
1711
- background: "radial-gradient(circle at 34% 28%,#ffffff,#c3cad0 50%,#7d848b)",
1712
- boxShadow: "inset 0 -1px 1px #00000044,0 1px 2px #00000077"
1713
- },
1714
- gold: {
1715
- background: "radial-gradient(circle at 34% 28%,#fffbe0,#e3c565 50%,#a5841d)",
1716
- boxShadow: "inset 0 -1px 1px #00000044,0 1px 2px #00000077,0 0 6px #c9a22788"
1717
- }
1995
+ copper: "inset 0 1px 0 #d99a5e88,inset 0 -2px 5px #0006,0 0 0 1px #5e2c12",
1996
+ silver: "inset 0 1px 0 #ffffffdd,inset 0 -2px 5px #0005,0 0 0 1px #6f767c",
1997
+ gold: "inset 0 1px 0 #fff8d8dd,inset 0 -2px 5px #0005,0 0 0 1px #8a6a15,0 3px 14px #c9a22766"
1718
1998
  };
1719
1999
  /** 一枚印章(button,点章弹卷)。未知组合直接抛错,不静默退化。 */
1720
2000
  function SealStamp(props) {
1721
- const face = FACE[props.role][props.tier];
1722
- if (face === void 0) throw new Error("未知印章组合:" + String(props.role) + "/" + String(props.tier));
2001
+ const palette = SEAL_PALETTE[props.role]?.[props.tier];
2002
+ if (palette === void 0) throw new Error("未知印章组合:" + String(props.role) + "/" + String(props.tier));
2003
+ const style = {
2004
+ display: "inline-flex",
2005
+ alignItems: "center",
2006
+ cursor: "pointer",
2007
+ fontWeight: 800,
2008
+ lineHeight: 1.2,
2009
+ whiteSpace: "nowrap",
2010
+ filter: "url(#dshLifeSealRoughEdge)",
2011
+ ...ROLE_BASE[props.role],
2012
+ background: palette.background,
2013
+ ...palette.border === void 0 ? {} : { border: palette.border },
2014
+ ...FACE_EXTRA[props.role][props.tier]
2015
+ };
2016
+ const tierName = props.tier === "copper" ? "铜" : props.tier === "silver" ? "银" : "金";
1723
2017
  const buttonProps = {
1724
2018
  type: "button",
1725
- style: {
1726
- display: "inline-flex",
1727
- alignItems: "center",
1728
- cursor: "pointer",
1729
- fontWeight: 800,
1730
- lineHeight: 1.2,
1731
- whiteSpace: "nowrap",
1732
- filter: "url(#dshLifeSealRoughEdge)",
1733
- ...ROLE_BASE[props.role],
1734
- ...face
1735
- },
2019
+ style,
2020
+ className: "dshLifeSealBtn",
1736
2021
  "aria-label": props.label,
1737
2022
  "aria-haspopup": "dialog",
2023
+ "data-tip": tierName + "章 · " + TIER_TEXT[props.tier],
1738
2024
  onClick: props.onSelect
1739
2025
  };
1740
2026
  if (props.role !== "plugin") return react.createElement("button", buttonProps, props.label);
@@ -1746,7 +2032,8 @@ window.__ModuleLoader__.load({
1746
2032
  right: "-1px",
1747
2033
  zIndex: 1,
1748
2034
  borderRadius: "4px",
1749
- ...BAND_EDGE[props.tier]
2035
+ boxShadow: BAND_EDGE[props.tier],
2036
+ background: palette.background
1750
2037
  };
1751
2038
  const rivet = {
1752
2039
  position: "absolute",
@@ -1756,13 +2043,10 @@ window.__ModuleLoader__.load({
1756
2043
  borderRadius: "50%",
1757
2044
  top: "50%",
1758
2045
  transform: "translateY(-50%)",
1759
- ...RIVET[props.tier]
2046
+ ...SEAL_RIVET[props.tier]
1760
2047
  };
1761
2048
  return react.createElement("button", buttonProps, react.createElement("span", {
1762
- style: {
1763
- ...band,
1764
- background: face.background
1765
- },
2049
+ style: band,
1766
2050
  "data-seal-band": "1"
1767
2051
  }), react.createElement("span", {
1768
2052
  style: {
@@ -2449,37 +2733,56 @@ window.__ModuleLoader__.load({
2449
2733
  const product = title ?? "";
2450
2734
  const seals = props.seals ?? [];
2451
2735
  const seed = props.sealSeed ?? 0;
2452
- /** 三行定序(六家一致:HELP 上/技能中/插件下;HELP 永远 z 最上)。 */
2736
+ /** 三行定序:**由面板按角色定**(HELP 上/技能中/插件下;HELP 永远 z 最上)。
2737
+ * 调用方给的数组顺序无关紧要,缺哪一枚就只画剩下的(位置顺次上移)——
2738
+ * 少给一枚不该把整块面板炸掉(旧写法按位置取 `seals[2]`,两枚时直接 TypeError)。 */
2453
2739
  const order = [
2454
- 0,
2455
- 1,
2456
- 2
2457
- ];
2458
- /** 三行行高(上/中/下;纵向净空各留约 10px,糙边抖动也碰不到)。 */
2459
- const TOPS = [
2460
- "0.3em",
2461
- "3.4em",
2462
- "6.2em"
2463
- ];
2464
- /** 轻微倾角(±0.5°~±2.5°,方向随机):家下标×槽位哈希,纯函数、每次同值,不过大。 */
2465
- const tiltOfSlot = (slot) => {
2740
+ "help",
2741
+ "skill",
2742
+ "plugin"
2743
+ ].map((role) => seals.findIndex((s) => s.role === role)).filter((index) => index >= 0);
2744
+ /** 三行槽位几何 · **唯一出处**:章的落位与卷轴浮层的锚点都从这里取,免得两处各写一套数字。
2745
+ * 行高(上/中/下;纵向净空各留约 10px,糙边抖动也碰不到)/轻微倾角(±0.5°~±2.5°,家下标×槽位哈希,
2746
+ * 纯函数每次同值)/右间距(三枚全部居右收敛,各家各行 0.2em~1.1em 错开,永不出面板右边界)。
2747
+ * `dialog` 是卷轴浮层那一份:贴该行下方 1.9em(章高约 1.5em + 0.4em 缝),右缘与那枚章对齐,
2748
+ * 再往左让 1.25em 给绦带(关闭钮骑在浮层右上方,不让它越出卡片)。 */
2749
+ const slotGeom = (slot, seed) => {
2750
+ const top = [
2751
+ "0.3em",
2752
+ "3.4em",
2753
+ "6.2em"
2754
+ ][slot] ?? "0.3em";
2466
2755
  const q = (seed * 5 + slot * 11 + 3) % 10;
2467
- return String((q % 2 === 0 ? -1 : 1) * (.5 + Math.floor(q / 2) * .5));
2756
+ const tilt = String((q % 2 === 0 ? -1 : 1) * (.5 + Math.floor(q / 2) * .5));
2757
+ const padRight = String(.2 + (seed * 7 + slot * 3 + 1) % 6 * .18) + "em";
2758
+ return {
2759
+ top,
2760
+ tilt,
2761
+ padRight,
2762
+ dialog: {
2763
+ top: "calc(" + top + " + 1.9em)",
2764
+ right: "calc(" + padRight + " + 1.25em)",
2765
+ left: "auto",
2766
+ justifyContent: "flex-end",
2767
+ maxWidth: "calc(100% - " + padRight + " - 1.75em)"
2768
+ }
2769
+ };
2468
2770
  };
2469
- /** 右间距(三枚全部居右收敛,各家各行 0.2em~1.1em 错开,永不出面板右边界)。 */
2470
- const padRightOfSlot = (slot) => String(.2 + (seed * 7 + slot * 3 + 1) % 6 * .18) + "em";
2471
2771
  const zOfRole = (role) => role === "help" ? 4 : role === "skill" ? 3 : 2;
2472
2772
  const overlay = seals.length === 0 ? null : react.createElement("div", { style: S.sealOverlay }, react.createElement(SealFilterDefs, {}), ...order.map((dataIndex, slot) => react.createElement("span", {
2473
2773
  key: seals[dataIndex].role + ":" + seals[dataIndex].tier,
2474
- style: {
2475
- position: "absolute",
2476
- top: TOPS[slot],
2477
- right: padRightOfSlot(slot),
2478
- zIndex: zOfRole(seals[dataIndex].role),
2479
- pointerEvents: "auto",
2480
- display: "inline-flex",
2481
- transform: "rotate(" + tiltOfSlot(slot) + "deg)"
2482
- }
2774
+ style: (() => {
2775
+ const g = slotGeom(slot, seed);
2776
+ return {
2777
+ position: "absolute",
2778
+ top: g.top,
2779
+ right: g.padRight,
2780
+ zIndex: zOfRole(seals[dataIndex].role),
2781
+ pointerEvents: "auto",
2782
+ display: "inline-flex",
2783
+ transform: "rotate(" + g.tilt + "deg)"
2784
+ };
2785
+ })()
2483
2786
  }, react.createElement("span", { style: {
2484
2787
  fontSize: "0.8em",
2485
2788
  display: "inline-flex"
@@ -2490,6 +2793,9 @@ window.__ModuleLoader__.load({
2490
2793
  onSelect: props.onSealSelect === void 0 ? void 0 : props.onSealSelect.bind(null, dataIndex)
2491
2794
  })))));
2492
2795
  const openSeal = props.selectedSeal === null || props.selectedSeal === void 0 ? null : seals[props.selectedSeal] ?? null;
2796
+ /** 卷轴浮层跟着**被点的那枚章**:锚点与章的落位同出一处(`slotGeom`),两处不会各自漂。 */
2797
+ const openSlot = props.selectedSeal === null || props.selectedSeal === void 0 ? -1 : order.indexOf(props.selectedSeal);
2798
+ const anchor = openSlot < 0 ? void 0 : slotGeom(openSlot, seed).dialog;
2493
2799
  const dialog = openSeal === null ? null : react.createElement(SealScrollDialog, {
2494
2800
  open: true,
2495
2801
  onClose: props.onSealClose ?? (() => void 0),
@@ -2498,7 +2804,8 @@ window.__ModuleLoader__.load({
2498
2804
  title: openSeal.label,
2499
2805
  progress: openSeal.progress,
2500
2806
  status: openSeal.status,
2501
- plan: openSeal.plan
2807
+ plan: openSeal.plan,
2808
+ anchor
2502
2809
  });
2503
2810
  const headDataDir = surface === null ? "" : surface.resolved?.["dbDir"] ?? surface.dataDir;
2504
2811
  const ready = state.kind === "ready";