koishi-plugin-haulmp-tools 1.0.0 → 1.0.2

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.
@@ -38,17 +38,17 @@ function annCard(row, ann, titleCn, bodyCn) {
38
38
  const body = bodyCn || ann.body || ''
39
39
  const tag = row.tag || 'VTC'
40
40
  const initial = String(tag.replace(/[^A-Za-z0-9\u4e00-\u9fa5]/g, '')[0] || 'V').toUpperCase()
41
- return `<!doctype html><html><head><meta charset="utf-8"><style>${cardTheme('#f59e0b', '#fbbf24', 720)}
42
- .head{display:flex;align-items:center;gap:14px;padding-bottom:16px;border-bottom:1px solid rgba(245,158,11,.18)}
43
- .ava{width:54px;height:54px;border-radius:13px;background:linear-gradient(150deg,rgba(245,158,11,.30),rgba(251,191,36,.12));border:1px solid rgba(245,158,11,.6);color:#ffd08a;font-weight:700;font-size:22px;display:flex;align-items:center;justify-content:center;flex:none;box-shadow:0 0 22px rgba(245,158,11,.3)}
41
+ return `<!doctype html><html><head><meta charset="utf-8"><style>${cardTheme('#d97706', '#f59e0b', 720)}
42
+ .head{display:flex;align-items:center;gap:14px;padding-bottom:16px;border-bottom:1px solid var(--edge)}
43
+ .ava{width:56px;height:56px;border-radius:18px;background:linear-gradient(150deg,rgba(255,255,255,.95),rgba(255,247,232,.7));border:1px solid rgba(217,119,6,.35);color:#92400e;font-weight:700;font-size:22px;display:flex;align-items:center;justify-content:center;flex:none;box-shadow:var(--panel-shadow)}
44
44
  .who{flex:1;min-width:0}
45
- .kicker{font-size:13px;color:#c9a86a;letter-spacing:1px}
46
- .cname{font-size:20px;font-weight:700;color:#fff;margin-top:3px}
47
- .cname .tag{color:#ffd08a;font-size:13px;font-weight:600;margin-left:8px}
48
- .stamp{width:60px;height:60px;border:2px solid rgba(245,158,11,.7);border-radius:50%;color:#ffd08a;font-size:14px;font-weight:700;display:flex;align-items:center;justify-content:center;letter-spacing:3px;transform:rotate(-12deg);box-shadow:0 0 20px rgba(245,158,11,.28),inset 0 0 16px rgba(245,158,11,.15);flex:none}
49
- .title{margin-top:20px;font-size:20px;font-weight:700;color:#fff;line-height:1.5;padding-left:14px;border-left:3px solid rgba(245,158,11,.7);text-shadow:0 2px 14px rgba(245,158,11,.25)}
50
- .body{margin-top:12px;font-size:14.5px;line-height:1.95;color:#cdd9e6;white-space:pre-wrap;padding:13px 17px;background:var(--glass-lo);border:1px solid rgba(245,158,11,.14);border-radius:12px}
51
- .foot{margin-top:18px;padding-top:12px;border-top:1px solid rgba(245,158,11,.16);display:flex;justify-content:space-between;font-size:11.5px;color:#62748a}
45
+ .kicker{font-size:13px;color:var(--t3);letter-spacing:1px}
46
+ .cname{font-size:20px;font-weight:700;color:var(--t1);margin-top:3px}
47
+ .cname .tag{color:#9a6b1f;font-size:13px;font-weight:600;margin-left:8px}
48
+ .stamp{width:62px;height:62px;border:2px solid rgba(217,119,6,.55);border-radius:50%;color:#b45309;font-size:14px;font-weight:700;display:flex;align-items:center;justify-content:center;letter-spacing:3px;transform:rotate(-12deg);background:rgba(255,255,255,.55);box-shadow:var(--panel-shadow);flex:none}
49
+ .title{margin-top:20px;font-size:20px;font-weight:700;color:var(--t1);line-height:1.5;padding-left:14px;border-left:4px solid rgba(217,119,6,.65)}
50
+ .body{margin-top:12px;font-size:14.5px;line-height:1.95;color:var(--t2);white-space:pre-wrap;padding:13px 17px;background:var(--glass-2);border:1px solid var(--stroke);border-radius:16px;box-shadow:var(--panel-shadow)}
51
+ .foot{margin-top:18px;padding-top:12px;border-top:1px solid var(--edge);display:flex;justify-content:space-between;font-size:11.5px;color:var(--t3)}
52
52
  </style></head><body><div id="card">${CARD_DECO}
53
53
  <div class="head"><div class="ava">${esc(initial)}</div><div class="who"><div class="kicker">车队公告</div><div class="cname">${esc(row.name || row.slug)}<span class="tag">${esc(tag)}</span></div></div><div class="stamp">公告</div></div>
54
54
  <div class="title">${esc(title)}</div>
@@ -1,23 +1,23 @@
1
1
  const { bindingQuery, esc, avatarUrl, resolveAvatar, renderCard, isText, cardTheme, CARD_DECO } = require('../util/common')
2
2
  const { fetchProfile } = require('../api/forumApi')
3
3
 
4
- // 🎨 设计提示词(绑定成功):成就解锁。翡翠绿庆典氛围,光环扩散 + 勾选徽记居中,
5
- // 光线收束到姓名,像游戏通关结算界面。底板用共享 ink 玻璃主题(深墨蓝 + 扫描线)。
6
- const CARD_CSS = cardTheme('#3aa86f', '#7fd4a0', 640) + `
7
- .hero{position:relative;height:150px;margin:-26px -28px 0;background:linear-gradient(120deg,rgba(58,168,111,.42),rgba(45,212,191,.24) 60%,rgba(7,16,26,.05));overflow:hidden}
8
- .hero::after{content:'';position:absolute;inset:0;background:radial-gradient(circle at 50% 135%,rgba(255,255,255,.20),transparent 60%)}
9
- .avatar{width:104px;height:104px;border-radius:50%;object-fit:cover;border:3px solid #14283b;background:#0f2233;box-shadow:0 0 0 3px rgba(94,211,154,.45),0 12px 40px rgba(0,0,0,.5),0 0 44px rgba(58,168,111,.4)}
10
- .avatar.ph{display:flex;align-items:center;justify-content:center;margin:0 auto;font-size:40px;font-weight:700;color:#dff8ea;background:linear-gradient(150deg,rgba(58,168,111,.45),rgba(45,212,191,.35))}
11
- h1{color:#fff;font-size:26px;margin-top:16px;letter-spacing:6px;text-align:center;text-shadow:0 2px 20px rgba(58,168,111,.4)}
12
- .uline{width:60px;height:3px;margin:11px auto 0;border-radius:2px;background:linear-gradient(90deg,transparent,var(--ac),transparent);box-shadow:0 0 12px var(--ac)}
13
- .name{margin-top:16px;font-size:25px;color:#fff;font-weight:700;text-align:center}
14
- .handle{font-size:14px;color:#9cc6f0;text-align:center;margin-top:4px}
4
+ // 🎨 设计提示词(绑定成功):入会证。薄荷绿柔光自顶部漫下 + 白色磨砂玻璃卡面,
5
+ // 头像用白色描边浮起,像一张清透的会员卡;文字全部深色,干净、明亮、有质感。
6
+ const CARD_CSS = cardTheme('#10b981', '#38bdf8', 640) + `
7
+ .hero{position:relative;height:150px;margin:-26px -28px 0;background:linear-gradient(120deg,rgba(16,185,129,.26),rgba(56,189,248,.20) 58%,rgba(255,255,255,.06));overflow:hidden}
8
+ .hero::after{content:'';position:absolute;inset:0;background:radial-gradient(circle at 50% 132%,rgba(255,255,255,.78),transparent 62%)}
9
+ .avatar{width:104px;height:104px;border-radius:50%;object-fit:cover;border:4px solid rgba(255,255,255,.95);background:#eef3f9;box-shadow:0 16px 34px -14px rgba(13,124,88,.5),0 0 0 1px rgba(124,144,196,.14)}
10
+ .avatar.ph{display:flex;align-items:center;justify-content:center;margin:0 auto;font-size:40px;font-weight:700;color:#0b7a5b;background:linear-gradient(150deg,rgba(16,185,129,.34),rgba(56,189,248,.26))}
11
+ h1{color:var(--t1);font-size:26px;margin-top:16px;letter-spacing:6px;text-align:center}
12
+ .uline{width:60px;height:3px;margin:11px auto 0;border-radius:2px;background:linear-gradient(90deg,transparent,var(--ac),transparent)}
13
+ .name{margin-top:16px;font-size:25px;color:var(--t1);font-weight:700;text-align:center}
14
+ .handle{font-size:14px;color:#3d7fbe;text-align:center;margin-top:4px}
15
15
  .ribbon{display:flex;gap:8px;justify-content:center;flex-wrap:wrap;margin-top:18px}
16
- .rchip{font-size:12.5px;color:#cfe3f7;background:var(--glass);border:1px solid var(--stroke-hi);border-radius:999px;padding:5px 14px;box-shadow:inset 0 1px 0 rgba(255,255,255,.07)}
17
- .rchip b{color:#fff}
18
- .hint{margin-top:18px;font-size:13px;color:#8ba0b5;line-height:1.85;text-align:center}
19
- .hint b{color:#cdd9e6}
20
- .foot{margin-top:22px;padding-top:13px;border-top:1px solid rgba(94,211,154,.16);display:flex;justify-content:space-between;font-size:12px;color:#62748a}
16
+ .rchip{font-size:12.5px;color:#42536f;background:var(--glass-2);border:1px solid var(--stroke);border-radius:999px;padding:5px 14px;box-shadow:var(--panel-shadow)}
17
+ .rchip b{color:var(--t1)}
18
+ .hint{margin-top:18px;font-size:13px;color:var(--t3);line-height:1.85;text-align:center}
19
+ .hint b{color:var(--t2)}
20
+ .foot{margin-top:22px;padding-top:13px;border-top:1px solid var(--edge);display:flex;justify-content:space-between;font-size:12px;color:var(--t3)}
21
21
  `
22
22
 
23
23
  function successCard(member, session, avatarSrc) {
@@ -48,18 +48,18 @@ function updateCard(status, notesCn, oldVersion) {
48
48
  const notes = notesCn || status.notes || '(本次更新无说明)'
49
49
  const nm = String(status.notes || '').match(/HaulMP\s+([\d.]+)/i)
50
50
  const verName = nm ? nm[1] : ''
51
- return `<!doctype html><html><head><meta charset="utf-8"><style>${cardTheme('#38bdf8', '#22d3ee', 720)}
52
- .head{display:flex;align-items:baseline;justify-content:space-between;padding-bottom:15px;border-bottom:1px solid rgba(56,189,248,.16)}
53
- .title{font-size:24px;font-weight:700;color:#fff;letter-spacing:1px}
54
- .brand{color:#6f9bb3;font-size:13px;font-weight:700;letter-spacing:2px}
51
+ return `<!doctype html><html><head><meta charset="utf-8"><style>${cardTheme('#0284c7', '#06b6d4', 720)}
52
+ .head{display:flex;align-items:baseline;justify-content:space-between;padding-bottom:15px;border-bottom:1px solid var(--edge)}
53
+ .title{font-size:24px;font-weight:700;color:var(--t1);letter-spacing:1px}
54
+ .brand{color:#2b6f96;font-size:13px;font-weight:700;letter-spacing:2px}
55
55
  .verline{display:flex;align-items:center;gap:14px;margin-top:18px}
56
- .vold{color:#8ba0b5;font-size:15px;padding:6px 13px;border:1px solid var(--stroke-hi);border-radius:10px}
57
- .arrow{color:#38bdf8;font-size:19px;font-weight:700;text-shadow:0 0 12px rgba(56,189,248,.7)}
58
- .vname{font-size:15px;font-weight:700;color:#9bdcff;background:rgba(56,189,248,.14);border:1px solid rgba(56,189,248,.45);border-radius:999px;padding:5px 13px;box-shadow:0 0 16px rgba(56,189,248,.2)}
59
- .vnew{font-size:22px;font-weight:800;color:#9bdcff;padding:6px 17px;background:linear-gradient(180deg,rgba(56,189,248,.25),rgba(56,189,248,.10));border:1px solid rgba(56,189,248,.55);border-radius:12px;box-shadow:0 0 26px rgba(56,189,248,.32)}
60
- .vinfo{margin-left:auto;color:#8ba0b5;font-size:12.5px;text-align:right;line-height:1.7}
61
- .notes{margin-top:16px;padding:14px 18px;background:var(--glass-lo);border:1px solid rgba(56,189,248,.16);border-left:3px solid rgba(56,189,248,.65);border-radius:10px;font-size:14.5px;line-height:1.9;color:#cdd9e6;white-space:pre-wrap}
62
- .foot{margin-top:18px;padding-top:12px;border-top:1px solid rgba(56,189,248,.16);display:flex;justify-content:space-between;font-size:11.5px;color:#62748a}
56
+ .vold{color:var(--t3);font-size:15px;padding:6px 13px;border:1px solid var(--edge);border-radius:12px;background:var(--glass-3)}
57
+ .arrow{color:#0284c7;font-size:19px;font-weight:700}
58
+ .vname{font-size:15px;font-weight:700;color:#075985;background:var(--glass-2);border:1px solid rgba(2,132,199,.35);border-radius:999px;padding:5px 13px;box-shadow:var(--panel-shadow)}
59
+ .vnew{font-size:22px;font-weight:800;color:#075985;padding:6px 17px;background:linear-gradient(180deg,rgba(255,255,255,.92),rgba(224,242,254,.75));border:1px solid rgba(2,132,199,.4);border-radius:16px;box-shadow:var(--panel-shadow)}
60
+ .vinfo{margin-left:auto;color:var(--t3);font-size:12.5px;text-align:right;line-height:1.7}
61
+ .notes{margin-top:16px;padding:14px 18px;background:var(--glass-2);border:1px solid var(--stroke);border-left:4px solid rgba(2,132,199,.75);border-radius:14px;font-size:14.5px;line-height:1.9;color:var(--t2);white-space:pre-wrap;box-shadow:var(--panel-shadow)}
62
+ .foot{margin-top:18px;padding-top:12px;border-top:1px solid var(--edge);display:flex;justify-content:space-between;font-size:11.5px;color:var(--t3)}
63
63
  </style></head><body><div id="card">${CARD_DECO}
64
64
  <div class="head"><span class="title">HaulMP 客户端更新</span><span class="brand">RELEASE</span></div>
65
65
  <div class="verline">${oldVersion ? `<span class="vold">v${oldVersion}</span><span class="arrow">→</span>` : ''}<span class="vnew">v${status.version}</span>${verName ? `<span class="vname">HaulMP ${verName}</span>` : ''}<span class="vinfo">发布于 ${fmtPublished(status.publishedAt) || '—'}<br>${fmtBytes(status.bytes)}</span></div>
@@ -2,37 +2,37 @@ const { esc, fmtTime, renderCard, bindingQuery, isText, cardTheme, CARD_DECO } =
2
2
  const { fetchCompany } = require('../api/vtcApi')
3
3
  const { loadSnap, saveSnap, makeSnap, rotateSnapshots, todayStr, fmtKm } = require('../database/companySnap')
4
4
 
5
- const cardCssFor = (accent) => cardTheme(accent || '#3aa86f', '#7fd4a0', 780) + `
5
+ const cardCssFor = (accent) => cardTheme(accent || '#0d9488', '#38bdf8', 780) + `
6
6
  .head{display:flex;align-items:baseline;justify-content:space-between}
7
- .title{font-size:23px;font-weight:700;color:#fff;letter-spacing:1px}
8
- .brand{color:#7f93a8;font-size:13px;font-weight:700;letter-spacing:2px}
9
- .sub{font-size:12.5px;color:#8ba0b5;margin-top:6px}
7
+ .title{font-size:23px;font-weight:700;color:var(--t1);letter-spacing:1px}
8
+ .brand{color:#5a6a8c;font-size:13px;font-weight:700;letter-spacing:2px}
9
+ .sub{font-size:12.5px;color:var(--t3);margin-top:6px}
10
10
  .podium{display:flex;align-items:flex-end;justify-content:center;gap:14px;margin-top:24px;padding:0 8px}
11
11
  .pod{flex:1;max-width:220px;text-align:center;min-width:0}
12
- .podium .pname{color:#fff;font-weight:700;font-size:15px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
13
- .podium .phandle{color:#9cc6f0;font-size:11.5px;margin-top:2px}
14
- .podium .pkm{color:#7fe0ac;font-weight:700;font-size:14px;margin:9px 0 9px;text-shadow:0 0 14px rgba(58,168,111,.45)}
15
- .pbase{border-radius:12px 12px 0 0;display:flex;align-items:flex-start;justify-content:center;padding-top:9px;font-size:21px;font-weight:800}
16
- .pod1 .pbase{height:76px;background:linear-gradient(180deg,rgba(232,185,106,.6),rgba(232,185,106,.10));color:#ffd98a;box-shadow:0 -8px 36px rgba(232,185,106,.4)}
17
- .pod2 .pbase{height:54px;background:linear-gradient(180deg,rgba(205,214,224,.42),rgba(205,214,224,.07));color:#e6ebf0}
18
- .pod3 .pbase{height:42px;background:linear-gradient(180deg,rgba(201,141,94,.5),rgba(201,141,94,.09));color:#eab98f}
19
- .board{margin-top:18px;background:var(--glass);border:1px solid var(--stroke);border-radius:12px;overflow:hidden;box-shadow:inset 0 1px 0 rgba(255,255,255,.05)}
12
+ .podium .pname{color:var(--t1);font-weight:700;font-size:15px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
13
+ .podium .phandle{color:#6b7ba0;font-size:11.5px;margin-top:2px}
14
+ .podium .pkm{color:#0f766e;font-weight:700;font-size:14px;margin:9px 0 9px}
15
+ .pbase{border-radius:14px 14px 0 0;display:flex;align-items:flex-start;justify-content:center;padding-top:9px;font-size:21px;font-weight:800;box-shadow:var(--panel-shadow)}
16
+ .pod1 .pbase{height:76px;background:linear-gradient(180deg,rgba(251,191,36,.85),rgba(255,255,255,.7));color:#8a4b06}
17
+ .pod2 .pbase{height:54px;background:linear-gradient(180deg,rgba(203,213,225,.9),rgba(255,255,255,.7));color:#475569}
18
+ .pod3 .pbase{height:42px;background:linear-gradient(180deg,rgba(251,146,60,.7),rgba(255,255,255,.7));color:#9a3412}
19
+ .board{margin-top:18px;background:var(--glass-2);border:1px solid var(--stroke);border-radius:18px;overflow:hidden;box-shadow:var(--panel-shadow)}
20
20
  .row{display:flex;align-items:center;gap:12px;padding:10px 16px}
21
- .row + .row{border-top:1px solid rgba(255,255,255,.06)}
22
- .row.me{background:linear-gradient(90deg,rgba(58,168,111,.14),rgba(74,144,217,.12))}
23
- .pos{width:34px;text-align:center;font-weight:700;font-size:14.5px;color:#7f93a8;flex:none}
21
+ .row + .row{border-top:1px solid var(--edge-2)}
22
+ .row.me{background:linear-gradient(90deg,rgba(13,148,136,.20),rgba(129,140,248,.16))}
23
+ .pos{width:34px;text-align:center;font-weight:700;font-size:14.5px;color:#5a6a8c;flex:none}
24
24
  .who{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
25
- .who b{color:#fff;font-size:14.5px;font-weight:600}
26
- .who span{color:#9cc6f0;font-size:12.5px;margin-left:7px}
27
- .km{font-size:14.5px;font-weight:700;color:#5fd39a;flex:none}
28
- .km small{color:#62748a;font-size:11.5px;font-weight:400;margin-left:8px}
29
- .you{display:inline-block;font-size:11px;color:#5fd39a;border:1px solid rgba(58,168,111,.5);background:rgba(58,168,111,.16);border-radius:999px;padding:1px 8px;margin-left:8px}
30
- .empty{padding:16px;color:#62748a;font-size:13px;text-align:center}
31
- .mine{margin-top:18px;background:linear-gradient(180deg,rgba(58,168,111,.10),rgba(7,14,22,.55));border:1px solid rgba(58,168,111,.4);border-radius:12px;padding:14px 16px;display:flex;align-items:center;justify-content:space-between;gap:12px;box-shadow:0 0 24px rgba(58,168,111,.12),inset 0 1px 0 rgba(255,255,255,.06)}
32
- .mine .lab{font-size:13px;color:#5fd39a;flex:none;letter-spacing:1px}
33
- .mine b{color:#fff;font-size:17px}
34
- .mine .note{font-size:12.5px;color:#8ba0b5}
35
- .foot{margin-top:18px;padding-top:12px;border-top:1px solid rgba(255,255,255,.10);display:flex;justify-content:space-between;font-size:12px;color:#62748a}
25
+ .who b{color:var(--t1);font-size:14.5px;font-weight:600}
26
+ .who span{color:#6b7ba0;font-size:12.5px;margin-left:7px}
27
+ .km{font-size:14.5px;font-weight:700;color:#0f766e;flex:none}
28
+ .km small{color:var(--t3);font-size:11.5px;font-weight:400;margin-left:8px}
29
+ .you{display:inline-block;font-size:11px;color:#0b7a5b;border:1px solid rgba(16,185,129,.45);background:rgba(255,255,255,.72);border-radius:999px;padding:1px 8px;margin-left:8px}
30
+ .empty{padding:16px;color:var(--t3);font-size:13px;text-align:center}
31
+ .mine{margin-top:18px;background:linear-gradient(180deg,rgba(255,255,255,.85),rgba(232,250,244,.62));border:1px solid rgba(13,148,136,.28);border-radius:18px;padding:14px 16px;display:flex;align-items:center;justify-content:space-between;gap:12px;box-shadow:var(--panel-shadow)}
32
+ .mine .lab{font-size:13px;color:#0f766e;flex:none;letter-spacing:1px;font-weight:600}
33
+ .mine b{color:var(--t1);font-size:17px}
34
+ .mine .note{font-size:12.5px;color:var(--t3)}
35
+ .foot{margin-top:18px;padding-top:12px;border-top:1px solid var(--edge);display:flex;justify-content:space-between;font-size:12px;color:var(--t3)}
36
36
  `
37
37
 
38
38
  function keyOf(session) {
@@ -162,19 +162,19 @@ function bindCard(company, rosterCount) {
162
162
  const founded = fmtDate(c.createdAt)
163
163
  const acc = c.accent || '#4a90d9'
164
164
  return `<!doctype html><html><head><meta charset="utf-8"><style>${cardTheme(acc, '#7fb4e6', 720)}
165
- .head{display:flex;align-items:center;gap:16px;padding-bottom:16px;border-bottom:1px solid var(--stroke)}
166
- .ava{width:64px;height:64px;border-radius:16px;background:linear-gradient(150deg,rgba(255,255,255,.2),rgba(255,255,255,.05));border:2px solid rgba(255,255,255,.28);color:#fff;font-weight:800;font-size:26px;display:flex;align-items:center;justify-content:center;flex:none;box-shadow:0 0 26px var(--ac)}
165
+ .head{display:flex;align-items:center;gap:16px;padding-bottom:16px;border-bottom:1px solid var(--edge)}
166
+ .ava{width:66px;height:66px;border-radius:20px;background:linear-gradient(150deg,rgba(255,255,255,.95),rgba(255,255,255,.6));border:1px solid var(--stroke);color:var(--t1);font-weight:800;font-size:26px;display:flex;align-items:center;justify-content:center;flex:none;box-shadow:var(--panel-shadow)}
167
167
  .who{flex:1;min-width:0}
168
- .kicker{font-size:13px;color:#cfe3f7;letter-spacing:1px}
169
- .cname{font-size:24px;font-weight:800;color:#fff;margin-top:3px;letter-spacing:.5px;text-shadow:0 2px 18px rgba(0,0,0,.4)}
170
- .cname .tag{color:#9cc6f0;font-size:13px;font-weight:600;margin-left:8px}
171
- .ok{flex:none;background:linear-gradient(180deg,rgba(58,168,111,.22),rgba(58,168,111,.08));border:1px solid rgba(94,211,154,.55);color:#7fe0ac;font-weight:700;font-size:13.5px;border-radius:999px;padding:7px 14px;box-shadow:0 0 18px rgba(58,168,111,.22)}
172
- .facts{display:flex;margin-top:18px;background:linear-gradient(180deg,rgba(74,144,217,.10),rgba(7,14,22,.55) 50%);border:1px solid rgba(74,144,217,.22);border-radius:14px;overflow:hidden;box-shadow:inset 0 1px 0 rgba(255,255,255,.05)}
168
+ .kicker{font-size:13px;color:var(--t3);letter-spacing:1px}
169
+ .cname{font-size:24px;font-weight:800;color:var(--t1);margin-top:3px;letter-spacing:.5px}
170
+ .cname .tag{color:#5a6a8c;font-size:13px;font-weight:600;margin-left:8px}
171
+ .ok{flex:none;background:rgba(255,255,255,.8);border:1px solid rgba(16,185,129,.45);color:#0b7a5b;font-weight:700;font-size:13.5px;border-radius:999px;padding:7px 14px;box-shadow:0 10px 22px -12px rgba(13,124,88,.6)}
172
+ .facts{display:flex;margin-top:18px;background:var(--glass-2);border:1px solid var(--stroke);border-radius:20px;overflow:hidden;box-shadow:var(--panel-shadow)}
173
173
  .fact{flex:1;text-align:center;padding:15px 8px;min-width:0}
174
- .fact + .fact{border-left:1px solid rgba(255,255,255,.07)}
175
- .fact b{display:block;font-size:22px;color:#fff;font-weight:800;text-shadow:0 2px 14px rgba(74,144,217,.35);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
176
- .fact span{display:block;font-size:12px;color:#93a7ba;margin-top:4px;letter-spacing:.5px}
177
- .foot{margin-top:18px;padding-top:12px;border-top:1px solid var(--stroke);display:flex;justify-content:space-between;font-size:11.5px;color:#62748a}
174
+ .fact + .fact{border-left:1px solid var(--edge-2)}
175
+ .fact b{display:block;font-size:22px;color:var(--t1);font-weight:800;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
176
+ .fact span{display:block;font-size:12px;color:var(--t3);margin-top:4px;letter-spacing:.5px}
177
+ .foot{margin-top:18px;padding-top:12px;border-top:1px solid var(--edge);display:flex;justify-content:space-between;font-size:11.5px;color:var(--t3)}
178
178
  </style></head><body><div id="card">${CARD_DECO}
179
179
  <div class="head"><div class="ava">${esc(initial)}</div><div class="who"><div class="kicker">车队绑定成功</div><div class="cname">${esc(c.name)}<span class="tag">${esc(tag)}</span></div></div><div class="ok">已绑定本群</div></div>
180
180
  <div class="facts">
@@ -42,18 +42,26 @@ async function syncTileVersion(ctx, force) {
42
42
  const mVer = js.match(/\/tiles\/\{z\}\/\{x\}\/\{y\}\.pbf\?v=([^"'`]+)/)
43
43
  if (!mVer) return
44
44
  const version = mVer[1]
45
- const current = await page.evaluate(() => window.getTileUrl())
46
- if (current && current.includes(version)) return
45
+ const current = await page.evaluate(() => (typeof window.getTileUrl === 'function' ? window.getTileUrl() : ''))
46
+ if (!current) {
47
+ ctx.logger('haulmp').warn('地图版本同步跳过:页面瓦片源为空(底图资源可能未加载)')
48
+ return
49
+ }
50
+ if (current.includes(version)) return
47
51
  const newUrl = `/proxy/tiles/{z}/{x}/{y}.pbf?v=${version}`
48
52
  const ok = await page.evaluate((u) => window.setTileUrl(u), newUrl)
49
- ctx.logger('haulmp').info('地图瓦片版本同步: %s -> %s (%s)', current ? current.split('v=')[1] : '无', version, ok ? 'ok' : 'fail')
53
+ ctx.logger('haulmp').info('地图瓦片版本同步: %s -> %s (%s)', current.split('v=')[1] || current, version, ok ? 'ok' : 'fail')
50
54
  } catch (e) {
51
55
  ctx.logger('haulmp').warn('地图版本同步失败: %s', e.message)
52
56
  }
53
57
  }
54
58
 
55
59
  let httpServer = null
60
+ let httpPort = 0
56
61
  let mapPage = null
62
+ let mapPageReady = false
63
+ let mapPageInit = null
64
+ let mapPageLock = Promise.resolve()
57
65
 
58
66
  function withTimeout(promise, ms, onTimeout) {
59
67
  let timer
@@ -68,8 +76,8 @@ function withTimeout(promise, ms, onTimeout) {
68
76
  ])
69
77
  }
70
78
 
71
- function startStaticServer() {
72
- if (httpServer) return
79
+ async function startStaticServer(ctx) {
80
+ if (httpServer) return httpPort
73
81
  // lib/command/locate.js → lib/resource/map;src/command/locate.js → src/resource/map(相对层级一致)
74
82
  const root = path.join(__dirname, '..', 'resource', 'map')
75
83
  httpServer = http.createServer((req, res) => {
@@ -116,34 +124,75 @@ function startStaticServer() {
116
124
  res.end('not found')
117
125
  }
118
126
  })
119
- httpServer.on('error', () => {})
120
- httpServer.listen(12665, '127.0.0.1')
127
+ // 端口随机分配(0 = 系统分配),避免 12665 被残留进程占用时静默失效
128
+ await new Promise((resolve) => {
129
+ httpServer.once('error', (e) => {
130
+ ctx.logger('haulmp').warn('地图本地服务启动失败: %s', e.message)
131
+ resolve(0)
132
+ })
133
+ httpServer.listen(0, '127.0.0.1', () => {
134
+ httpPort = httpServer.address().port
135
+ resolve(httpPort)
136
+ })
137
+ })
138
+ return httpPort
121
139
  }
122
140
 
123
141
  async function getMapPage(ctx) {
142
+ // 排队锁:并发调用(启动预热 / 版本同步 / 定位)串行初始化,避免拿到半成品页面
143
+ const run = mapPageLock.then(() => doGetMapPage(ctx), () => doGetMapPage(ctx))
144
+ mapPageLock = run.then(() => {}, () => {})
145
+ return run
146
+ }
147
+
148
+ async function doGetMapPage(ctx) {
124
149
  let puppeteer = null
125
150
  try {
126
151
  puppeteer = ctx.get('puppeteer')
127
152
  } catch (e) { /* ignore */ }
128
153
  if (!puppeteer) return null
129
- startStaticServer()
130
- if (mapPage) {
154
+ const port = await startStaticServer(ctx)
155
+ if (!port) return null
156
+ if (mapPage && mapPageReady) {
131
157
  try {
132
158
  if (!mapPage.isClosed()) {
133
159
  ctx.logger('haulmp').info('使用已就绪地图页面')
134
160
  return mapPage
135
161
  }
136
162
  } catch (e) { /* ignore */ }
163
+ mapPage = null
164
+ mapPageReady = false
137
165
  }
166
+ if (mapPage) {
167
+ // 上一次初始化失败残留的半成品页面,丢弃重建
168
+ try { mapPage.close().catch(() => {}) } catch (e) { /* ignore */ }
169
+ mapPage = null
170
+ mapPageReady = false
171
+ }
172
+
138
173
  ctx.logger('haulmp').info('重建地图页面…')
139
- mapPage = await puppeteer.page()
140
174
  const t0 = Date.now()
141
- await mapPage.setViewport({ width: 720, height: 500, deviceScaleFactor: 2 })
142
- await mapPage.goto('http://127.0.0.1:12665/', { waitUntil: 'domcontentloaded', timeout: 30000 })
143
- await mapPage.waitForFunction(() => window.__ready === true, { timeout: 30000 })
144
- ctx.logger('haulmp').info('地图页面构建完成 %dms', Date.now() - t0)
145
- preloadEurope(mapPage).catch(() => {})
146
- return mapPage
175
+ mapPageReady = false
176
+ mapPage = await puppeteer.page()
177
+ try {
178
+ mapPage.on('pageerror', (err) => {
179
+ ctx.logger('haulmp').warn('地图页面脚本错误: %s', err && err.message ? err.message : String(err))
180
+ })
181
+ await mapPage.setViewport({ width: 720, height: 500, deviceScaleFactor: 2 })
182
+ await mapPage.goto(`http://127.0.0.1:${port}/`, { waitUntil: 'domcontentloaded', timeout: 30000 })
183
+ // __ready(底图 load 事件)与 getTileUrl(模块执行完成)都满足才算就绪
184
+ await mapPage.waitForFunction(() => window.__ready === true && typeof window.getTileUrl === 'function', { timeout: 30000 })
185
+ mapPageReady = true
186
+ ctx.logger('haulmp').info('地图页面构建完成 %dms', Date.now() - t0)
187
+ preloadEurope(mapPage).catch(() => {})
188
+ return mapPage
189
+ } catch (e) {
190
+ ctx.logger('haulmp').warn('地图页面初始化失败: %s', e.message)
191
+ try { mapPage.close().catch(() => {}) } catch (err) { /* ignore */ }
192
+ mapPage = null
193
+ mapPageReady = false
194
+ return null
195
+ }
147
196
  }
148
197
 
149
198
  async function preloadEurope(page) {
@@ -176,6 +225,7 @@ async function doMapShot(ctx, info, p, nearby) {
176
225
  const resetPage = () => {
177
226
  try { page.close().catch(() => {}) } catch (e) { /* ignore */ }
178
227
  mapPage = null
228
+ mapPageReady = false
179
229
  }
180
230
  const t1 = Date.now()
181
231
  await withTimeout(
@@ -199,6 +249,7 @@ async function doMapShot(ctx, info, p, nearby) {
199
249
  if (mapPage) {
200
250
  try { mapPage.close().catch(() => {}) } catch (err) { /* ignore */ }
201
251
  mapPage = null
252
+ mapPageReady = false
202
253
  }
203
254
  return null
204
255
  }
@@ -78,18 +78,18 @@ function notifyCard(row, items, unread) {
78
78
  const unreadCls = i.readAt ? '' : ' on'
79
79
  return `<div class="nrow${unreadCls}"><span class="nk">${esc(kind)}</span><span class="nt">${esc(actor)}:${esc(i.title || '')}</span><span class="nw">${fmtWhen(i.createdAt)}</span></div>`
80
80
  }).join('')
81
- return `<!doctype html><html><head><meta charset="utf-8"><style>${cardTheme('#4a90d9', '#8b5cf6', 720)}
82
- .head{display:flex;align-items:baseline;justify-content:space-between;padding-bottom:15px;border-bottom:1px solid rgba(74,144,217,.18)}
83
- .title{font-size:24px;font-weight:700;color:#fff;letter-spacing:1px}
84
- .badge{background:linear-gradient(180deg,rgba(74,144,217,.22),rgba(74,144,217,.08));border:1px solid rgba(74,144,217,.5);color:#9cc6f0;font-weight:700;font-size:14px;border-radius:999px;padding:5px 14px}
81
+ return `<!doctype html><html><head><meta charset="utf-8"><style>${cardTheme('#4f46e5', '#7c3aed', 720)}
82
+ .head{display:flex;align-items:baseline;justify-content:space-between;padding-bottom:15px;border-bottom:1px solid var(--edge)}
83
+ .title{font-size:24px;font-weight:700;color:var(--t1);letter-spacing:1px}
84
+ .badge{background:linear-gradient(180deg,rgba(255,255,255,.92),rgba(238,242,255,.7));border:1px solid rgba(79,70,229,.32);color:#3730a3;font-weight:700;font-size:14px;border-radius:999px;padding:5px 14px;box-shadow:var(--panel-shadow)}
85
85
  .rows{margin-top:14px}
86
- .nrow{display:flex;align-items:center;gap:12px;padding:11px 4px;border-bottom:1px solid rgba(255,255,255,.06)}
87
- .nrow.on .nk{color:#7fe0ac}
88
- .nk{color:#7fb4e6;font-weight:700;font-size:13px;flex:none;width:118px}
89
- .nt{flex:1;min-width:0;color:#dbe6f0;font-size:13.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
90
- .nw{color:#62748a;font-size:12px;flex:none}
91
- .empty{padding:22px;text-align:center;color:#62748a;font-size:14px}
92
- .foot{margin-top:16px;padding-top:12px;border-top:1px solid rgba(74,144,217,.18);display:flex;justify-content:space-between;font-size:11.5px;color:#62748a}
86
+ .nrow{display:flex;align-items:center;gap:12px;padding:11px 4px;border-bottom:1px solid var(--edge-2)}
87
+ .nrow.on .nk{color:#047857}
88
+ .nk{color:#4a3fc0;font-weight:700;font-size:13px;flex:none;width:118px}
89
+ .nt{flex:1;min-width:0;color:var(--t2);font-size:13.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
90
+ .nw{color:var(--t3);font-size:12px;flex:none}
91
+ .empty{padding:22px;text-align:center;color:var(--t3);font-size:14px}
92
+ .foot{margin-top:16px;padding-top:12px;border-top:1px solid var(--edge);display:flex;justify-content:space-between;font-size:11.5px;color:var(--t3)}
93
93
  </style></head><body><div id="card">${CARD_DECO}
94
94
  <div class="head"><span class="title">我的通知</span><span class="badge">${unread} 条未读</span></div>
95
95
  <div class="rows">${rows || '<div class="empty">暂无通知</div>'}</div>
@@ -118,24 +118,24 @@ function driveCard(row, me, game) {
118
118
  const span = x.closed_at ? `${fmtWhen(x.started_at)} → ${fmtWhen(x.closed_at)}` : `${fmtWhen(x.started_at)} · 进行中`
119
119
  return `<div class="srow"><span class="sspan">${esc(span)}</span><span class="ssrv">${esc(x.server_name || '')}</span><span class="skm">${fmtKm(x.distance_km)} km</span><span class="sdur">${dur}</span></div>`
120
120
  }).join('')
121
- return `<!doctype html><html><head><meta charset="utf-8"><style>${cardTheme('#3aa86f', '#38bdf8', 760)}
122
- .head{display:flex;align-items:baseline;justify-content:space-between;padding-bottom:15px;border-bottom:1px solid rgba(58,168,111,.18)}
123
- .title{font-size:24px;font-weight:700;color:#fff;letter-spacing:1px}
121
+ return `<!doctype html><html><head><meta charset="utf-8"><style>${cardTheme('#059669', '#0284c7', 760)}
122
+ .head{display:flex;align-items:baseline;justify-content:space-between;padding-bottom:15px;border-bottom:1px solid var(--edge)}
123
+ .title{font-size:24px;font-weight:700;color:var(--t1);letter-spacing:1px}
124
124
  .state{font-size:13px;font-weight:700;border-radius:999px;padding:5px 13px}
125
- .state.on{color:#7fe0ac;background:rgba(58,168,111,.14);border:1px solid rgba(94,211,154,.5)}
126
- .state.off{color:#8ba0b5;background:var(--glass-lo);border:1px solid var(--stroke-hi)}
125
+ .state.on{color:#0b7a5b;background:rgba(255,255,255,.85);border:1px solid rgba(16,185,129,.42);box-shadow:var(--panel-shadow)}
126
+ .state.off{color:var(--t3);background:var(--glass-3);border:1px solid var(--edge)}
127
127
  .stats{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-top:18px}
128
- .st{background:var(--glass-lo);border:1px solid var(--stroke);border-radius:13px;padding:12px 8px;text-align:center;box-shadow:inset 0 1px 0 rgba(255,255,255,.07)}
129
- .st b{display:block;font-size:20px;color:#fff;font-weight:800}
130
- .st span{display:block;font-size:11.5px;color:#8ba0b5;margin-top:4px}
131
- .sec{margin-top:18px;font-size:12.5px;color:#7fe0ac;font-weight:700;letter-spacing:2px}
132
- .srow{display:flex;align-items:center;gap:12px;padding:10px 4px;border-bottom:1px solid rgba(255,255,255,.06);font-size:13px}
133
- .sspan{flex:1;min-width:0;color:#dbe6f0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
134
- .ssrv{color:#9cc6f0;flex:none}
135
- .skm{color:#5fd39a;font-weight:700;flex:none}
136
- .sdur{color:#8ba0b5;flex:none;width:90px;text-align:right}
137
- .empty{padding:18px;text-align:center;color:#62748a;font-size:13.5px}
138
- .foot{margin-top:16px;padding-top:12px;border-top:1px solid rgba(58,168,111,.18);display:flex;justify-content:space-between;font-size:11.5px;color:#62748a}
128
+ .st{background:var(--glass-2);border:1px solid var(--stroke);border-radius:18px;padding:12px 8px;text-align:center;box-shadow:var(--panel-shadow)}
129
+ .st b{display:block;font-size:20px;color:var(--t1);font-weight:800}
130
+ .st span{display:block;font-size:11.5px;color:var(--t3);margin-top:4px}
131
+ .sec{margin-top:18px;font-size:12.5px;color:#0f766e;font-weight:700;letter-spacing:2px}
132
+ .srow{display:flex;align-items:center;gap:12px;padding:10px 4px;border-bottom:1px solid var(--edge-2);font-size:13px}
133
+ .sspan{flex:1;min-width:0;color:var(--t2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
134
+ .ssrv{color:#5a6a8c;flex:none}
135
+ .skm{color:#047857;font-weight:700;flex:none}
136
+ .sdur{color:var(--t3);flex:none;width:90px;text-align:right}
137
+ .empty{padding:18px;text-align:center;color:var(--t3);font-size:13.5px}
138
+ .foot{margin-top:16px;padding-top:12px;border-top:1px solid var(--edge);display:flex;justify-content:space-between;font-size:11.5px;color:var(--t3)}
139
139
  </style></head><body><div id="card">${CARD_DECO}
140
140
  <div class="head"><span class="title">我的行驶记录</span><span class="state ${recording ? 'on' : 'off'}">${recording ? `正在录制 · ${esc(recording.server_name || '')}` : '未连接驾驶'}</span></div>
141
141
  <div class="stats">
@@ -318,7 +318,7 @@ function apply(ctx, config) {
318
318
  })
319
319
 
320
320
  ctx.command('找回密码', '私信重置 HaulMP 密码(恢复码,无邮件重置)')
321
- .alias('找回密码h', '找回密码H', '重置密码')
321
+ .alias('找回密码h', '找回密码H')
322
322
  .action(async ({ session }) => {
323
323
  if (!session) return
324
324
  if (!isPrivate(session)) return '请私聊机器人发送「找回密码」'
@@ -384,4 +384,4 @@ function apply(ctx, config) {
384
384
  })
385
385
  }
386
386
 
387
- module.exports = { apply }
387
+ module.exports = { apply, notifyCard, notifyText, driveCard, driveText }
@@ -38,18 +38,18 @@ function menuCard() {
38
38
  const rows = items.map(([cmd, desc]) => `<div class="mrow"><span class="cmd">${cmd}</span><span class="desc">${desc}</span></div>`).join('')
39
39
  return `<div class="mtitle">${title}</div>${rows}`
40
40
  }).join('')
41
- return `<!doctype html><html><head><meta charset="utf-8"><style>${cardTheme('#6366f1', '#a78bfa', 780)}
42
- .head{display:flex;align-items:baseline;justify-content:space-between;padding-bottom:15px;border-bottom:1px solid rgba(163,166,255,.16)}
43
- .title{font-size:24px;font-weight:700;color:#fff;letter-spacing:1px}
44
- .brand{color:#8b90c8;font-size:13px;font-weight:700;letter-spacing:2px}
41
+ return `<!doctype html><html><head><meta charset="utf-8"><style>${cardTheme('#6366f1', '#a855f7', 780)}
42
+ .head{display:flex;align-items:baseline;justify-content:space-between;padding-bottom:15px;border-bottom:1px solid var(--edge)}
43
+ .title{font-size:24px;font-weight:700;color:var(--t1);letter-spacing:1px}
44
+ .brand{color:#6b6fd0;font-size:13px;font-weight:700;letter-spacing:2px}
45
45
  .mgrid{display:grid;grid-template-columns:1fr 1fr;gap:9px;margin-top:14px}
46
- .mtitle{grid-column:1 / -1;display:flex;align-items:center;gap:9px;font-size:12.5px;color:#a5a9f5;font-weight:700;letter-spacing:2px;margin-top:10px}
46
+ .mtitle{grid-column:1 / -1;display:flex;align-items:center;gap:9px;font-size:12.5px;color:#4a4fb0;font-weight:700;letter-spacing:2px;margin-top:10px}
47
47
  .mtitle:first-child{margin-top:0}
48
- .mtitle::before{content:'';width:3px;height:12px;border-radius:2px;background:linear-gradient(180deg,var(--ac),var(--ac2));box-shadow:0 0 9px var(--ac)}
49
- .mrow{background:linear-gradient(180deg,rgba(99,102,241,.13),rgba(7,14,22,.55) 58%);border:1px solid rgba(99,102,241,.22);border-radius:11px;padding:9px 12px;min-width:0;box-shadow:inset 0 1px 0 rgba(255,255,255,.05)}
50
- .cmd{display:block;font-size:13px;color:#c7caff;font-weight:700;font-family:Consolas,"Cascadia Mono",monospace;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
51
- .desc{display:block;font-size:11.5px;color:#8ba0b5;margin-top:3px;line-height:1.45}
52
- .foot{margin-top:17px;padding-top:12px;border-top:1px solid rgba(163,166,255,.16);display:flex;justify-content:space-between;font-size:11.5px;color:#6f7395}
48
+ .mtitle::before{content:'';width:3px;height:13px;border-radius:2px;background:linear-gradient(180deg,var(--ac),var(--ac2))}
49
+ .mrow{background:var(--glass-2);border:1px solid var(--stroke);border-radius:16px;padding:9px 12px;min-width:0;box-shadow:var(--panel-shadow)}
50
+ .cmd{display:block;font-size:13px;color:#3f43a8;font-weight:700;font-family:Consolas,"Cascadia Mono",monospace;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
51
+ .desc{display:block;font-size:11.5px;color:var(--t3);margin-top:3px;line-height:1.45}
52
+ .foot{margin-top:17px;padding-top:12px;border-top:1px solid var(--edge);display:flex;justify-content:space-between;font-size:11.5px;color:var(--t3)}
53
53
  </style></head><body><div id="card">${CARD_DECO}
54
54
  <div class="head"><span class="title">HaulMP 功能菜单</span><span class="brand">MENU</span></div>
55
55
  <div class="mgrid">${sections}</div>
@@ -69,8 +69,8 @@ function menuText() {
69
69
  }
70
70
 
71
71
  function apply(ctx, config) {
72
- ctx.command('菜单', 'HaulMP 功能菜单')
73
- .alias('menuh', '菜单H')
72
+ ctx.command('菜单h', 'HaulMP 功能菜单')
73
+ .alias('menuh')
74
74
  .action(async ({ session }) => {
75
75
  if (!session) return
76
76
  if (!isText(config, 'menu')) {
@@ -65,51 +65,51 @@ const BADGES = [
65
65
  ['still_truckin', '初心不改', (m, now) => !!m.memberSince && now - new Date(m.memberSince).getTime() >= 365 * 864e5 && !!m.lastActive && now - new Date(m.lastActive).getTime() < 30 * 864e5],
66
66
  ]
67
67
 
68
- const CARD_CSS = cardTheme('#4a90d9', '#8b5cf6', 820) + `
69
- .hero{position:relative;margin:-26px -28px 0;height:112px;background:linear-gradient(120deg,rgba(74,144,217,.45),rgba(139,92,246,.32) 62%,rgba(7,16,26,.05))}
70
- .hero::after{content:'';position:absolute;inset:0;background:radial-gradient(circle at 80% -30%,rgba(255,255,255,.22),transparent 55%)}
68
+ const CARD_CSS = cardTheme('#4f46e5', '#0ea5e9', 820) + `
69
+ .hero{position:relative;margin:-26px -28px 0;height:112px;background:linear-gradient(120deg,rgba(99,102,241,.28),rgba(14,165,233,.22) 62%,rgba(255,255,255,.06))}
70
+ .hero::after{content:'';position:absolute;inset:0;background:radial-gradient(circle at 78% -28%,rgba(255,255,255,.82),transparent 58%)}
71
71
  .hero-row{position:relative;display:block;height:100%;padding:0;z-index:2}
72
- .avatar{position:absolute;left:28px;top:10px;width:92px;height:92px;border-radius:24px;object-fit:cover;border:3px solid rgba(255,255,255,.38);background:#0f2233;box-shadow:0 10px 30px rgba(0,0,0,.4),0 0 30px rgba(74,144,217,.4)}
73
- .avatar.ph{display:flex;align-items:center;justify-content:center;font-size:38px;font-weight:700;color:#dbe9fb;background:linear-gradient(150deg,rgba(74,144,217,.4),rgba(139,92,246,.35))}
72
+ .avatar{position:absolute;left:28px;top:10px;width:92px;height:92px;border-radius:24px;object-fit:cover;border:4px solid rgba(255,255,255,.95);background:#eef3f9;box-shadow:0 16px 32px -14px rgba(49,60,120,.5)}
73
+ .avatar.ph{display:flex;align-items:center;justify-content:center;font-size:38px;font-weight:700;color:#3730a3;background:linear-gradient(150deg,rgba(99,102,241,.32),rgba(14,165,233,.28))}
74
74
  .hero-who{position:absolute;left:140px;right:170px;top:50%;transform:translateY(-50%);min-width:0}
75
- .name{font-size:26px;font-weight:700;color:#fff;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-shadow:0 2px 16px rgba(0,0,0,.5)}
76
- .handle{font-size:14px;color:#cfe3f7;font-weight:400;margin-left:8px}
75
+ .name{font-size:26px;font-weight:700;color:var(--t1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
76
+ .handle{font-size:14px;color:#6b7ba0;font-weight:400;margin-left:8px}
77
77
  .hero-right{position:absolute;top:12px;right:28px;display:flex;gap:10px;align-items:center}
78
- .brand{color:rgba(255,255,255,.6);font-size:13px;font-weight:700;letter-spacing:2px}
79
- .bound{display:inline-block;font-size:12px;color:#8af0b8;border:1px solid rgba(94,211,154,.6);background:rgba(20,40,30,.45);border-radius:999px;padding:2px 10px;box-shadow:0 0 12px rgba(58,168,111,.35)}
80
- .status{font-size:13.5px;color:#cfe3f7;margin-top:6px}
81
- .dot{display:inline-block;width:9px;height:9px;border-radius:50%;margin-right:7px;background:#8fa3b8}
82
- .dot.on{background:#3aa86f;box-shadow:0 0 0 4px rgba(58,168,111,.18),0 0 10px rgba(58,168,111,.9)}
78
+ .brand{color:rgba(60,74,120,.55);font-size:13px;font-weight:700;letter-spacing:2px}
79
+ .bound{display:inline-block;font-size:12px;color:#0b7a5b;border:1px solid rgba(16,185,129,.45);background:rgba(255,255,255,.72);border-radius:999px;padding:2px 10px;box-shadow:0 8px 18px -10px rgba(13,124,88,.55)}
80
+ .status{font-size:13.5px;color:var(--t2);margin-top:6px}
81
+ .dot{display:inline-block;width:9px;height:9px;border-radius:50%;margin-right:7px;background:#a8b3c7}
82
+ .dot.on{background:#10b981;box-shadow:0 0 0 4px rgba(16,185,129,.18)}
83
83
  .chips{display:flex;flex-wrap:wrap;gap:8px;margin-top:16px}
84
- .chip{font-size:12.5px;color:#9cc6f0;background:linear-gradient(180deg,rgba(74,144,217,.22),rgba(74,144,217,.10));border:1px solid rgba(74,144,217,.4);border-radius:999px;padding:3px 12px;white-space:nowrap;box-shadow:0 0 14px rgba(74,144,217,.15),inset 0 1px 0 rgba(255,255,255,.06)}
85
- .chip.gold{color:#f0c887;background:linear-gradient(180deg,rgba(224,138,46,.2),rgba(224,138,46,.08));border-color:rgba(224,138,46,.45);box-shadow:0 0 14px rgba(224,138,46,.18)}
84
+ .chip{font-size:12.5px;color:#3730a3;background:linear-gradient(180deg,rgba(255,255,255,.88),rgba(255,255,255,.58));border:1px solid rgba(99,102,241,.28);border-radius:999px;padding:3px 12px;white-space:nowrap;box-shadow:0 8px 18px -12px rgba(58,76,140,.55),inset 0 1px 0 rgba(255,255,255,.9)}
85
+ .chip.gold{color:#9a5b06;background:linear-gradient(180deg,rgba(255,255,255,.88),rgba(255,248,232,.62));border-color:rgba(217,119,6,.32)}
86
86
  .stats{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-top:16px}
87
- .stat{position:relative;overflow:hidden;background:linear-gradient(180deg,rgba(74,144,217,.13),rgba(7,14,22,.55) 46%);border:1px solid rgba(74,144,217,.22);border-radius:12px;padding:13px 8px 11px;text-align:center;box-shadow:inset 0 1px 0 rgba(255,255,255,.06)}
88
- .stat::before{content:'';position:absolute;top:0;left:20%;right:20%;height:2px;background:linear-gradient(90deg,transparent,var(--ac),transparent)}
89
- .stat b{display:block;font-size:23px;color:#fff;font-weight:700;text-shadow:0 2px 14px rgba(74,144,217,.35)}
90
- .stat span{display:block;font-size:12px;color:#93a7ba;margin-top:4px;letter-spacing:.5px}
87
+ .stat{position:relative;overflow:hidden;background:var(--glass-2);border:1px solid var(--stroke);border-radius:18px;padding:13px 8px 11px;text-align:center;box-shadow:var(--panel-shadow)}
88
+ .stat::before{content:'';position:absolute;top:0;left:22%;right:22%;height:3px;border-radius:0 0 3px 3px;background:linear-gradient(90deg,transparent,var(--ac),var(--ac2),transparent)}
89
+ .stat b{display:block;font-size:23px;color:var(--t1);font-weight:700}
90
+ .stat span{display:block;font-size:12px;color:var(--t3);margin-top:4px;letter-spacing:.5px}
91
91
  .top{display:flex;gap:26px;margin-top:20px;align-items:flex-start}
92
92
  .tcol{flex:1;min-width:0}
93
93
  .tcol.wide{flex:1.35}
94
94
  .section{margin-top:20px}
95
95
  .top .section{margin-top:0}
96
96
  .section:first-child{margin-top:20px}
97
- .section h3{display:flex;align-items:center;gap:9px;font-size:12.5px;color:#9cc6f0;font-weight:700;letter-spacing:2px;margin-bottom:10px}
98
- .section h3::before{content:'';width:3px;height:12px;border-radius:2px;background:linear-gradient(180deg,var(--ac),var(--ac2));box-shadow:0 0 9px var(--ac)}
97
+ .section h3{display:flex;align-items:center;gap:9px;font-size:12.5px;color:#3f4f7a;font-weight:700;letter-spacing:2px;margin-bottom:10px}
98
+ .section h3::before{content:'';width:3px;height:13px;border-radius:2px;background:linear-gradient(180deg,var(--ac),var(--ac2))}
99
99
  .kv{font-size:13.5px;line-height:1.85}
100
100
  .kv .row{white-space:normal;word-break:break-all}
101
- .kv b{color:#cdd9e6;font-weight:600;margin-right:7px}
102
- .haul{background:var(--glass);border:1px solid rgba(74,144,217,.16);border-radius:12px;overflow:hidden;box-shadow:inset 0 1px 0 rgba(255,255,255,.05)}
101
+ .kv b{color:var(--t2);font-weight:600;margin-right:7px}
102
+ .haul{background:var(--glass-2);border:1px solid var(--stroke);border-radius:16px;overflow:hidden;box-shadow:var(--panel-shadow)}
103
103
  .haul-row{display:flex;align-items:center;gap:12px;padding:10px 14px}
104
- .haul-row + .haul-row{border-top:1px solid rgba(255,255,255,.06)}
104
+ .haul-row + .haul-row{border-top:1px solid var(--edge-2)}
105
105
  .haul-main{flex:1;min-width:0}
106
- .haul-route{color:#fff;font-weight:600;font-size:13.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
107
- .haul-cargo{color:#8ba0b5;font-size:12px;margin-top:3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
108
- .haul-meta{text-align:right;font-size:12px;color:#8ba0b5;flex:none;line-height:1.55}
109
- .ok{color:#3aa86f}
110
- .cancel{color:#e05656}
111
- .empty{color:#62748a;font-size:13px;padding:12px 14px;background:var(--glass-lo);border:1px solid rgba(74,144,217,.14);border-radius:12px}
112
- .foot{margin-top:4px;padding-top:13px;border-top:1px solid rgba(255,255,255,.10);display:flex;justify-content:space-between;font-size:12px;color:#62748a}
106
+ .haul-route{color:var(--t1);font-weight:600;font-size:13.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
107
+ .haul-cargo{color:var(--t3);font-size:12px;margin-top:3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
108
+ .haul-meta{text-align:right;font-size:12px;color:var(--t3);flex:none;line-height:1.55}
109
+ .ok{color:#0b9a6b;font-weight:600}
110
+ .cancel{color:#dc2626;font-weight:600}
111
+ .empty{color:var(--t3);font-size:13px;padding:12px 14px;background:var(--glass-3);border:1px solid var(--stroke);border-radius:14px}
112
+ .foot{margin-top:4px;padding-top:13px;border-top:1px solid var(--edge);display:flex;justify-content:space-between;font-size:12px;color:var(--t3)}
113
113
  `
114
114
 
115
115
  function computeBadges(m, staff, now) {
@@ -253,7 +253,7 @@ function profileCard(data, extras, opts) {
253
253
  const rs = (data.recent || []).slice(0, 3)
254
254
  let postsHtml = ''
255
255
  if (rs.length) {
256
- postsHtml = `<div class="section"><h3>最近发帖</h3><div class="kv">${rs.map((p) => `<div class="row full">「${esc(p.title)}」<span style="color:#62748a">${esc(p.categoryName || '')} · ${fmtTime(p.createdAt)}</span></div>`).join('')}</div></div>`
256
+ postsHtml = `<div class="section"><h3>最近发帖</h3><div class="kv">${rs.map((p) => `<div class="row full">「${esc(p.title)}」<span style="color:var(--t3)">${esc(p.categoryName || '')} · ${fmtTime(p.createdAt)}</span></div>`).join('')}</div></div>`
257
257
  }
258
258
 
259
259
  return `<!doctype html><html><head><meta charset="utf-8"><style>${CARD_CSS}</style></head><body><div id="card">${CARD_DECO}
@@ -431,7 +431,7 @@ function apply(ctx, config) {
431
431
 
432
432
  ctx.command('查询H [handle]', '查询 HaulMP 账号信息')
433
433
  .alias('queryh', '查询h')
434
- .usage('不填时查询已绑定的账号;例如:查询H / 查询H 注册账号(如 gebilaohei) / 查询H @群友')
434
+ .usage('不填时查询已绑定的账号;例如:查询H / 查询H @群友')
435
435
  .action(async ({ session }, handle) => {
436
436
  if (!session) return
437
437
  const raw = String(handle || '').trim()
@@ -480,7 +480,7 @@ function apply(ctx, config) {
480
480
  if (data) target = resolved
481
481
  }
482
482
  }
483
- if (!data) return `没找到「${target}」。支持:注册账号(如 gebilaohei)/ 游戏ID / QQ号(已绑定群友)/ @群友`
483
+ if (!data) return `没找到「${target}」。你可以试试:私聊注册账号`
484
484
 
485
485
  let extras = {}
486
486
  let avatarSrc = ''
@@ -1,35 +1,35 @@
1
1
  const { esc, fmtTime, bindingQuery, renderCard, isText, cardTheme, CARD_DECO } = require('../util/common')
2
2
  const { ensureCache } = require('../database/rankCache')
3
3
 
4
- const CARD_CSS = cardTheme('#e8b96a', '#f0a04b', 760) + `
4
+ const CARD_CSS = cardTheme('#d97706', '#f59e0b', 760) + `
5
5
  .head{display:flex;align-items:baseline;justify-content:space-between}
6
- .title{font-size:24px;font-weight:700;color:#fff;letter-spacing:1px}
7
- .brand{color:#a8925f;font-size:13px;font-weight:700;letter-spacing:2px}
8
- .sub{font-size:12.5px;color:#8ba0b5;margin-top:6px}
6
+ .title{font-size:24px;font-weight:700;color:var(--t1);letter-spacing:1px}
7
+ .brand{color:#9a6b1f;font-size:13px;font-weight:700;letter-spacing:2px}
8
+ .sub{font-size:12.5px;color:var(--t3);margin-top:6px}
9
9
  .podium{display:flex;align-items:flex-end;justify-content:center;gap:14px;margin-top:26px;padding:0 8px}
10
10
  .pod{flex:1;max-width:215px;text-align:center;min-width:0}
11
- .podium .pname{color:#fff;font-weight:700;font-size:15px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
12
- .podium .phandle{color:#9cc6f0;font-size:11.5px;margin-top:2px}
13
- .podium .pkm{color:#f0c887;font-weight:700;font-size:14px;margin:9px 0 9px;text-shadow:0 0 14px rgba(232,185,106,.4)}
14
- .pbase{border-radius:12px 12px 0 0;display:flex;align-items:flex-start;justify-content:center;padding-top:9px;font-size:21px;font-weight:800}
15
- .pod1 .pbase{height:76px;background:linear-gradient(180deg,rgba(232,185,106,.6),rgba(232,185,106,.10));color:#ffd98a;box-shadow:0 -8px 36px rgba(232,185,106,.4)}
16
- .pod2 .pbase{height:54px;background:linear-gradient(180deg,rgba(205,214,224,.42),rgba(205,214,224,.07));color:#e6ebf0}
17
- .pod3 .pbase{height:42px;background:linear-gradient(180deg,rgba(201,141,94,.5),rgba(201,141,94,.09));color:#eab98f}
18
- .board{margin-top:20px;background:var(--glass);border:1px solid rgba(232,185,106,.16);border-radius:12px;overflow:hidden;box-shadow:inset 0 1px 0 rgba(255,255,255,.05)}
11
+ .podium .pname{color:var(--t1);font-weight:700;font-size:15px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
12
+ .podium .phandle{color:#6b7ba0;font-size:11.5px;margin-top:2px}
13
+ .podium .pkm{color:#b45309;font-weight:700;font-size:14px;margin:9px 0 9px}
14
+ .pbase{border-radius:14px 14px 0 0;display:flex;align-items:flex-start;justify-content:center;padding-top:9px;font-size:21px;font-weight:800;box-shadow:var(--panel-shadow)}
15
+ .pod1 .pbase{height:76px;background:linear-gradient(180deg,rgba(251,191,36,.85),rgba(255,255,255,.7));color:#8a4b06}
16
+ .pod2 .pbase{height:54px;background:linear-gradient(180deg,rgba(203,213,225,.9),rgba(255,255,255,.7));color:#475569}
17
+ .pod3 .pbase{height:42px;background:linear-gradient(180deg,rgba(251,146,60,.7),rgba(255,255,255,.7));color:#9a3412}
18
+ .board{margin-top:20px;background:var(--glass-2);border:1px solid var(--stroke);border-radius:18px;overflow:hidden;box-shadow:var(--panel-shadow)}
19
19
  .row{display:flex;align-items:center;gap:12px;padding:10px 16px}
20
- .row + .row{border-top:1px solid rgba(255,255,255,.06)}
21
- .row.me{background:linear-gradient(90deg,rgba(232,185,106,.12),rgba(74,144,217,.12))}
22
- .pos{width:34px;text-align:center;font-weight:700;font-size:14.5px;color:#a8925f;flex:none}
20
+ .row + .row{border-top:1px solid var(--edge-2)}
21
+ .row.me{background:linear-gradient(90deg,rgba(251,191,36,.22),rgba(129,140,248,.16))}
22
+ .pos{width:34px;text-align:center;font-weight:700;font-size:14.5px;color:#9a6b1f;flex:none}
23
23
  .who{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
24
- .who b{color:#fff;font-size:14.5px;font-weight:600}
25
- .who span{color:#9cc6f0;font-size:12.5px;margin-left:7px}
26
- .km{font-size:14.5px;font-weight:700;color:#f0c887;flex:none}
27
- .you{display:inline-block;font-size:11px;color:#5fd39a;border:1px solid rgba(58,168,111,.5);background:rgba(58,168,111,.16);border-radius:999px;padding:1px 8px;margin-left:8px}
28
- .mine{margin-top:18px;background:linear-gradient(180deg,rgba(232,185,106,.10),rgba(7,14,22,.55));border:1px solid rgba(232,185,106,.4);border-radius:12px;padding:14px 16px;display:flex;align-items:center;justify-content:space-between;gap:12px;box-shadow:0 0 24px rgba(232,185,106,.12),inset 0 1px 0 rgba(255,255,255,.06)}
29
- .mine .lab{font-size:13px;color:#f0c887;flex:none;letter-spacing:1px}
30
- .mine b{color:#fff;font-size:17px}
31
- .mine .note{font-size:12.5px;color:#8ba0b5}
32
- .foot{margin-top:18px;padding-top:12px;border-top:1px solid rgba(255,255,255,.10);display:flex;justify-content:space-between;font-size:12px;color:#62748a}
24
+ .who b{color:var(--t1);font-size:14.5px;font-weight:600}
25
+ .who span{color:#6b7ba0;font-size:12.5px;margin-left:7px}
26
+ .km{font-size:14.5px;font-weight:700;color:#b45309;flex:none}
27
+ .you{display:inline-block;font-size:11px;color:#0b7a5b;border:1px solid rgba(16,185,129,.45);background:rgba(255,255,255,.72);border-radius:999px;padding:1px 8px;margin-left:8px}
28
+ .mine{margin-top:18px;background:linear-gradient(180deg,rgba(255,255,255,.85),rgba(255,248,232,.6));border:1px solid rgba(217,119,6,.32);border-radius:18px;padding:14px 16px;display:flex;align-items:center;justify-content:space-between;gap:12px;box-shadow:var(--panel-shadow)}
29
+ .mine .lab{font-size:13px;color:#9a5b06;flex:none;letter-spacing:1px;font-weight:600}
30
+ .mine b{color:var(--t1);font-size:17px}
31
+ .mine .note{font-size:12.5px;color:var(--t3)}
32
+ .foot{margin-top:18px;padding-top:12px;border-top:1px solid var(--edge);display:flex;justify-content:space-between;font-size:12px;color:var(--t3)}
33
33
  `
34
34
 
35
35
  function fmtKm(n) {
@@ -1,23 +1,23 @@
1
1
  const { esc, fmtTime, fetchJsonMemo, renderCard, isText, cardTheme, CARD_DECO } = require('../util/common')
2
2
 
3
- const CARD_CSS = cardTheme('#3aa86f', '#2dd4bf', 720) + `
3
+ const CARD_CSS = cardTheme('#059669', '#0891b2', 720) + `
4
4
  .head{display:flex;align-items:baseline;justify-content:space-between;margin-bottom:2px}
5
- .title{font-size:23px;font-weight:700;color:#fff;letter-spacing:1px}
6
- .brand{color:#6f9a86;font-size:13px;font-weight:700;letter-spacing:2px}
5
+ .title{font-size:23px;font-weight:700;color:var(--t1);letter-spacing:1px}
6
+ .brand{color:#2f7d6b;font-size:13px;font-weight:700;letter-spacing:2px}
7
7
  .hero-num{text-align:center;padding:16px 0 6px}
8
- .ring{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;border-radius:50%;border:1px solid rgba(58,168,111,.55);box-shadow:0 0 20px rgba(58,168,111,.35);margin-bottom:8px}
9
- .dot{width:10px;height:10px;border-radius:50%;background:#8fa3b8}
10
- .dot.on{background:#3aa86f;box-shadow:0 0 12px rgba(58,168,111,1)}
11
- .big{font-size:66px;font-weight:800;color:#fff;line-height:1.02;text-shadow:0 8px 44px rgba(58,168,111,.55)}
12
- .cap{color:#93a7ba;font-size:13px;margin-top:6px;letter-spacing:3px}
13
- .hstate{margin-top:9px;font-size:12.5px;color:#5fd39a;letter-spacing:2px}
8
+ .ring{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;border-radius:50%;background:var(--glass-2);border:1px solid var(--stroke);box-shadow:var(--panel-shadow);margin-bottom:8px}
9
+ .dot{width:10px;height:10px;border-radius:50%;background:#a8b3c7}
10
+ .dot.on{background:#10b981;box-shadow:0 0 0 5px rgba(16,185,129,.18)}
11
+ .big{font-size:66px;font-weight:800;line-height:1.02;background:linear-gradient(150deg,#0f766e,#0ea5e9);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
12
+ .cap{color:var(--t3);font-size:13px;margin-top:6px;letter-spacing:3px}
13
+ .hstate{margin-top:9px;font-size:12.5px;color:#0b7a5b;letter-spacing:2px;font-weight:600}
14
14
  .tiles{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:18px}
15
- .tile{background:linear-gradient(180deg,rgba(58,168,111,.11),rgba(7,14,22,.55) 52%);border:1px solid rgba(58,168,111,.2);border-radius:12px;padding:11px 14px;box-shadow:inset 0 1px 0 rgba(255,255,255,.05)}
16
- .tile .t{font-size:12px;color:#7fa896;letter-spacing:1px}
17
- .tile .v{font-size:15px;color:#eef6f1;font-weight:600;margin-top:5px}
18
- .on{color:#5fd39a;text-shadow:0 0 10px rgba(58,168,111,.6)}
19
- .off{color:#e05656}
20
- .foot{margin-top:18px;padding-top:12px;border-top:1px solid rgba(255,255,255,.10);display:flex;justify-content:space-between;font-size:12px;color:#62748a}
15
+ .tile{background:var(--glass-2);border:1px solid var(--stroke);border-radius:18px;padding:11px 14px;box-shadow:var(--panel-shadow)}
16
+ .tile .t{font-size:12px;color:var(--t3);letter-spacing:1px}
17
+ .tile .v{font-size:15px;color:var(--t1);font-weight:600;margin-top:5px}
18
+ .on{color:#0b7a5b;font-weight:700}
19
+ .off{color:#dc2626;font-weight:700}
20
+ .foot{margin-top:18px;padding-top:12px;border-top:1px solid var(--edge);display:flex;justify-content:space-between;font-size:12px;color:var(--t3)}
21
21
  `
22
22
 
23
23
  function fmtBytes(n) {
@@ -87,23 +87,23 @@ function trafficCard(list) {
87
87
  rows.push(`<div class="r"><i style="background:${KINDS[k].color};box-shadow:0 0 10px ${KINDS[k].color}"></i><span class="k">${KINDS[k].zh}</span><span class="city">${esc(it.city)} 附近</span><span class="rm${soon ? ' soon' : ''}">${it.remain ? `剩 ${it.remain} 分钟` : ''}</span></div>`)
88
88
  }
89
89
  }
90
- return `<!doctype html><html><head><meta charset="utf-8"><style>${cardTheme('#ef6a6a', '#f5a623', width)}
91
- .head{display:flex;align-items:center;justify-content:space-between;padding-bottom:15px;border-bottom:1px solid var(--stroke)}
92
- .title{font-size:24px;font-weight:700;color:#fff;letter-spacing:1px}
93
- .badge{background:linear-gradient(180deg,rgba(245,166,35,.24),rgba(245,166,35,.10));border:1px solid rgba(245,166,35,.55);color:#ffcb7a;font-weight:700;font-size:15px;border-radius:999px;padding:6px 15px;box-shadow:0 0 20px rgba(245,166,35,.28)}
90
+ return `<!doctype html><html><head><meta charset="utf-8"><style>${cardTheme('#e11d48', '#f59e0b', width)}
91
+ .head{display:flex;align-items:center;justify-content:space-between;padding-bottom:15px;border-bottom:1px solid var(--edge)}
92
+ .title{font-size:24px;font-weight:700;color:var(--t1);letter-spacing:1px}
93
+ .badge{background:linear-gradient(180deg,rgba(255,255,255,.92),rgba(255,241,242,.72));border:1px solid rgba(225,29,72,.32);color:#9f1239;font-weight:700;font-size:15px;border-radius:999px;padding:6px 15px;box-shadow:var(--panel-shadow)}
94
94
  .chips{display:flex;flex-wrap:wrap;gap:8px;margin-top:15px}
95
- .chip{background:var(--glass-lo);border:1px solid var(--stroke);border-radius:999px;padding:5px 13px;font-size:13px;color:#aebdcb;box-shadow:inset 0 1px 0 rgba(255,255,255,.06)}
96
- .chip b{color:#fff;margin-left:4px}
95
+ .chip{background:var(--glass-2);border:1px solid var(--stroke);border-radius:999px;padding:5px 13px;font-size:13px;color:var(--t2);box-shadow:var(--panel-shadow)}
96
+ .chip b{color:var(--t1);margin-left:4px}
97
97
  .chip i{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:7px;vertical-align:middle}
98
- .list{margin-top:15px;background:var(--glass-lo);border:1px solid var(--stroke);border-radius:14px;overflow:hidden;column-count:${cols};column-gap:0;box-shadow:inset 0 1px 0 rgba(255,255,255,.06),0 8px 24px rgba(0,0,0,.3)}
98
+ .list{margin-top:15px;background:var(--glass-2);border:1px solid var(--stroke);border-radius:20px;overflow:hidden;column-count:${cols};column-gap:0;box-shadow:var(--panel-shadow)}
99
99
  .r{display:flex;align-items:center;gap:12px;padding:9px 16px;break-inside:avoid}
100
- .r + .r{border-top:1px solid rgba(255,255,255,.05)}
100
+ .r + .r{border-top:1px solid var(--edge-2)}
101
101
  .r > i{width:9px;height:9px;border-radius:50%;flex:none}
102
- .r .k{width:52px;font-size:13.5px;font-weight:600;color:#cdd9e6;flex:none}
103
- .r .city{flex:1;min-width:0;font-size:13.5px;color:#eef4f9;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
104
- .r .rm{font-size:12.5px;color:#8ba0b5;flex:none}
105
- .r .rm.soon{color:#ffcb7a;font-weight:700;text-shadow:0 0 10px rgba(245,166,35,.6)}
106
- .foot{margin-top:16px;padding-top:12px;border-top:1px solid var(--stroke);display:flex;justify-content:space-between;font-size:11.5px;color:#5f7186}
102
+ .r .k{width:52px;font-size:13.5px;font-weight:600;color:var(--t2);flex:none}
103
+ .r .city{flex:1;min-width:0;font-size:13.5px;color:var(--t1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
104
+ .r .rm{font-size:12.5px;color:var(--t3);flex:none}
105
+ .r .rm.soon{color:#b45309;font-weight:700}
106
+ .foot{margin-top:16px;padding-top:12px;border-top:1px solid var(--edge);display:flex;justify-content:space-between;font-size:11.5px;color:var(--t3)}
107
107
  </style></head><body><div id="card">${CARD_DECO}
108
108
  <div class="head"><span class="title">HaulMP 实时路况</span><span class="badge">${list.length} 处</span></div>
109
109
  <div class="chips">${chips}</div>
@@ -64,19 +64,19 @@ function versionCard(v) {
64
64
  c.bytes ? fmtBytes(c.bytes) : '',
65
65
  c.publishedAt ? `发布 ${fmtWhen(c.publishedAt)}` : '',
66
66
  ].filter(Boolean).join(' · ')
67
- return `<!doctype html><html><head><meta charset="utf-8"><style>${cardTheme('#38bdf8', '#22d3ee', 720)}
68
- .head{display:flex;align-items:baseline;justify-content:space-between;padding-bottom:15px;border-bottom:1px solid rgba(56,189,248,.16)}
69
- .title{font-size:24px;font-weight:700;color:#fff;letter-spacing:1px}
70
- .brand{color:#6f9bb3;font-size:13px;font-weight:700;letter-spacing:2px}
67
+ return `<!doctype html><html><head><meta charset="utf-8"><style>${cardTheme('#0284c7', '#06b6d4', 720)}
68
+ .head{display:flex;align-items:baseline;justify-content:space-between;padding-bottom:15px;border-bottom:1px solid var(--edge)}
69
+ .title{font-size:24px;font-weight:700;color:var(--t1);letter-spacing:1px}
70
+ .brand{color:#2b6f96;font-size:13px;font-weight:700;letter-spacing:2px}
71
71
  .hero-v{text-align:center;padding:18px 0 4px}
72
- .hlab{font-size:13px;color:#7fb8d0;letter-spacing:4px}
73
- .hbig{font-size:58px;font-weight:800;line-height:1.1;margin-top:8px;background:linear-gradient(180deg,#fff,#9bdcff);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;filter:drop-shadow(0 8px 30px rgba(56,189,248,.45))}
74
- .hsub{color:#8ba0b5;font-size:12.5px;margin-top:10px}
72
+ .hlab{font-size:13px;color:var(--t3);letter-spacing:4px}
73
+ .hbig{font-size:58px;font-weight:800;line-height:1.1;margin-top:8px;background:linear-gradient(150deg,#0369a1,#06b6d4);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
74
+ .hsub{color:var(--t3);font-size:12.5px;margin-top:10px}
75
75
  .tiles{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:22px}
76
- .tile{background:var(--glass-lo);border:1px solid var(--stroke);border-radius:14px;padding:16px 12px;text-align:center;box-shadow:inset 0 1px 0 rgba(255,255,255,.07),0 8px 20px rgba(0,0,0,.24)}
77
- .tile .t{font-size:12.5px;color:#8ba0b5;letter-spacing:1px}
78
- .tile .v{font-size:27px;font-weight:800;color:#fff;margin-top:8px;text-shadow:0 2px 18px rgba(56,189,248,.35)}
79
- .foot{margin-top:20px;padding-top:12px;border-top:1px solid rgba(56,189,248,.16);display:flex;justify-content:space-between;font-size:11.5px;color:#62748a}
76
+ .tile{background:var(--glass-2);border:1px solid var(--stroke);border-radius:20px;padding:16px 12px;text-align:center;box-shadow:var(--panel-shadow)}
77
+ .tile .t{font-size:12.5px;color:var(--t3);letter-spacing:1px}
78
+ .tile .v{font-size:27px;font-weight:800;color:var(--t1);margin-top:8px}
79
+ .foot{margin-top:20px;padding-top:12px;border-top:1px solid var(--edge);display:flex;justify-content:space-between;font-size:11.5px;color:var(--t3)}
80
80
  </style></head><body><div id="card">${CARD_DECO}
81
81
  <div class="head"><span class="title">版本信息</span><span class="brand">VERSION</span></div>
82
82
  <div class="hero-v"><div class="hlab">HAULMP 客户端</div><div class="hbig">${esc(big)}</div><div class="hsub">${esc(sub || '—')}</div></div>
@@ -115,18 +115,18 @@ function saveState(ctx, st) {
115
115
 
116
116
  function gameCard(changes) {
117
117
  const rows = changes.map(([g, from, to]) => `<div class="grow"><span class="gname">${esc(g)}</span><span class="gfrom">${esc(from)}</span><span class="garrow">→</span><span class="gnew">${esc(to)}</span></div>`).join('')
118
- return `<!doctype html><html><head><meta charset="utf-8"><style>${cardTheme('#3aa86f', '#38bdf8', 720)}
119
- .head{display:flex;align-items:baseline;justify-content:space-between;padding-bottom:15px;border-bottom:1px solid rgba(58,168,111,.18)}
120
- .title{font-size:24px;font-weight:700;color:#fff;letter-spacing:1px}
121
- .brand{color:#6f9a86;font-size:13px;font-weight:700;letter-spacing:2px}
118
+ return `<!doctype html><html><head><meta charset="utf-8"><style>${cardTheme('#059669', '#0284c7', 720)}
119
+ .head{display:flex;align-items:baseline;justify-content:space-between;padding-bottom:15px;border-bottom:1px solid var(--edge)}
120
+ .title{font-size:24px;font-weight:700;color:var(--t1);letter-spacing:1px}
121
+ .brand{color:#2f7d6b;font-size:13px;font-weight:700;letter-spacing:2px}
122
122
  .rows{margin-top:18px}
123
- .grow{display:flex;align-items:center;gap:14px;background:var(--glass-lo);border:1px solid var(--stroke);border-radius:14px;padding:16px 18px;box-shadow:inset 0 1px 0 rgba(255,255,255,.07),0 8px 20px rgba(0,0,0,.24)}
123
+ .grow{display:flex;align-items:center;gap:14px;background:var(--glass-2);border:1px solid var(--stroke);border-radius:20px;padding:16px 18px;box-shadow:var(--panel-shadow)}
124
124
  .grow + .grow{margin-top:12px}
125
- .gname{flex:1;color:#fff;font-weight:700;font-size:16px}
126
- .gfrom{color:#8ba0b5;font-size:15px;text-decoration:line-through;opacity:.85}
127
- .garrow{color:#5fd39a;font-weight:700;font-size:17px;text-shadow:0 0 12px rgba(58,168,111,.7)}
128
- .gnew{font-size:17px;font-weight:800;color:#7fe0ac;text-shadow:0 0 16px rgba(58,168,111,.5)}
129
- .foot{margin-top:18px;padding-top:12px;border-top:1px solid rgba(58,168,111,.18);display:flex;justify-content:space-between;font-size:11.5px;color:#62748a}
125
+ .gname{flex:1;color:var(--t1);font-weight:700;font-size:16px}
126
+ .gfrom{color:var(--t3);font-size:15px;text-decoration:line-through;opacity:.9}
127
+ .garrow{color:#0b7a5b;font-weight:700;font-size:17px}
128
+ .gnew{font-size:17px;font-weight:800;color:#047857}
129
+ .foot{margin-top:18px;padding-top:12px;border-top:1px solid var(--edge);display:flex;justify-content:space-between;font-size:11.5px;color:var(--t3)}
130
130
  </style></head><body><div id="card">${CARD_DECO}
131
131
  <div class="head"><span class="title">游戏版本更新</span><span class="brand">GAME</span></div>
132
132
  <div class="rows">${rows}</div>
@@ -3,24 +3,24 @@ const fs = require('fs')
3
3
  const path = require('path')
4
4
  const { FONT } = require('./constant')
5
5
 
6
- // 🎨 共享卡片主题:深墨蓝玻璃拟态(与 ets2-tools-tmp 同一设计语言家族,但保留区分度):
7
- // - 底色偏蓝青的 ink 渐变(ets2 为偏紫的 #0c1426 家族)
8
- // - 卡片自带极细扫描线网格( haulmp 签名元素,ets2 没有)
9
- // - 顶部 accent 光带 + 双光斑极光
10
- // - 圆角 20px(ets2 为 18/13/10 体系)
6
+ // 🎨 共享卡片主题:浅色极光玻璃拟态(Light Aurora Glass)
7
+ // - 底色:柔和彩色极光渐变(靛蓝 / 天蓝 / 薄荷 / 琥珀 / 粉),直接画在 body 上
8
+ // - 卡面:白色磨砂玻璃(半透明 + backdrop-filter 强模糊 + 白色描边 + 内外双高光 + 柔和投影)
9
+ // - 装饰:顶部极光光带 + 两团柔光(沿用 .glow / .orb 结构,改为亮色柔光)
10
+ // - 每张卡仍保留各自的强调色(--ac / --ac2)
11
11
  function cardTheme(accent, accent2, width) {
12
12
  const a2 = accent2 || accent
13
- return `:root{--ac:${accent};--ac2:${a2};--glass:rgba(255,255,255,.05);--glass-lo:rgba(255,255,255,.02);--stroke:rgba(255,255,255,.09);--stroke-hi:rgba(255,255,255,.18);--t1:#e8f2fc;--t2:#a9bdd2;--t3:#7c90a6}
13
+ return `:root{--ac:${accent};--ac2:${a2};--glass:rgba(255,255,255,.68);--glass-2:rgba(255,255,255,.52);--glass-3:rgba(255,255,255,.34);--stroke:rgba(255,255,255,.95);--edge:rgba(124,144,196,.22);--edge-2:rgba(124,144,196,.13);--t1:#16203a;--t2:#48587a;--t3:#7b8aa5;--card-shadow:0 26px 64px -24px rgba(58,76,140,.50);--panel-shadow:0 12px 30px -16px rgba(58,76,140,.36),inset 0 1px 0 rgba(255,255,255,.95)}
14
14
  *{margin:0;padding:0;box-sizing:border-box}
15
- body{background:#04070c;font-family:${FONT}}
16
- #card{position:relative;overflow:hidden;width:${width || 720}px;background:linear-gradient(168deg,#0e1b2a 0%,#0a1521 55%,#071018 100%);padding:26px 28px 16px;color:#cdd9e6}
17
- #card::before{content:'';position:absolute;inset:0;z-index:0;pointer-events:none;background:repeating-linear-gradient(0deg,rgba(255,255,255,.013) 0 1px,transparent 1px 28px),repeating-linear-gradient(90deg,rgba(255,255,255,.013) 0 1px,transparent 1px 28px)}
15
+ body{background-color:#eef2fc;background-image:radial-gradient(48% 42% at 8% 2%,rgba(129,140,248,.62),transparent 64%),radial-gradient(44% 40% at 94% 6%,rgba(56,189,248,.52),transparent 62%),radial-gradient(50% 46% at 90% 94%,rgba(52,211,153,.48),transparent 64%),radial-gradient(46% 40% at 6% 98%,rgba(251,191,36,.44),transparent 62%),radial-gradient(40% 36% at 50% 50%,rgba(244,114,182,.30),transparent 66%),radial-gradient(34% 30% at 62% 12%,rgba(167,139,250,.40),transparent 64%);background-repeat:no-repeat;font-family:${FONT}}
16
+ #card{position:relative;overflow:hidden;width:${width || 720}px;padding:26px 28px 16px;color:var(--t2);border-radius:26px;background:linear-gradient(150deg,rgba(255,255,255,.60),rgba(248,251,255,.38) 48%,rgba(255,255,255,.54));border:1px solid var(--stroke);box-shadow:var(--card-shadow),inset 0 1px 0 rgba(255,255,255,.98),inset 0 -1px 0 rgba(255,255,255,.35);backdrop-filter:saturate(190%) blur(26px);-webkit-backdrop-filter:saturate(190%) blur(26px)}
17
+ #card::before{content:'';position:absolute;inset:0;z-index:0;pointer-events:none;background:linear-gradient(180deg,rgba(255,255,255,.55),transparent 24%),linear-gradient(115deg,rgba(255,255,255,.42) 0%,rgba(255,255,255,.06) 34%,transparent 58%)}
18
18
  #card>*{position:relative;z-index:1}
19
19
  #card>.glow,#card>.orb{z-index:0}
20
- #card>.glow{position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,transparent,var(--ac) 30%,var(--ac2) 70%,transparent);box-shadow:0 2px 26px var(--ac)}
21
- #card>.orb{position:absolute;border-radius:50%;filter:blur(85px);pointer-events:none}
22
- #card>.orb1{width:320px;height:320px;background:var(--ac);top:-150px;right:-100px;opacity:.20}
23
- #card>.orb2{width:260px;height:260px;background:var(--ac2);bottom:-130px;left:-80px;opacity:.12}`
20
+ #card>.glow{position:absolute;top:0;left:0;right:0;height:4px;background:linear-gradient(90deg,transparent,var(--ac) 26%,var(--ac2) 74%,transparent);opacity:.9;box-shadow:0 6px 24px -4px var(--ac)}
21
+ #card>.orb{position:absolute;border-radius:50%;filter:blur(72px);pointer-events:none}
22
+ #card>.orb1{width:330px;height:330px;background:var(--ac);top:-180px;right:-120px;opacity:.34}
23
+ #card>.orb2{width:270px;height:270px;background:var(--ac2);bottom:-160px;left:-100px;opacity:.26}`
24
24
  }
25
25
 
26
26
  const CARD_DECO = '<div class="glow"></div><div class="orb orb1"></div><div class="orb orb2"></div>'
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-haulmp-tools",
3
3
  "description": "欧卡HaulMP联机平台信息查询",
4
- "version": "1.0.0",
4
+ "version": "1.0.2",
5
5
  "contributors": [
6
6
  "opwop <slhp1013@qq.com>"
7
7
  ],