koishi-plugin-ets2-tools-tmp 3.1.2 → 3.1.3

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.
@@ -4,158 +4,330 @@
4
4
  <meta charset="UTF-8">
5
5
  <title>Server List</title>
6
6
  <style>
7
- * {
8
- margin: 0;
9
- padding: 0;
10
- box-sizing: border-box;
11
- }
7
+ * { margin: 0; padding: 0; box-sizing: border-box; }
8
+
12
9
  body {
13
10
  font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
14
- background-color: #0b1120;
11
+ background: #080810;
12
+ -webkit-font-smoothing: antialiased;
15
13
  }
14
+
16
15
  #container {
17
- width: 380px;
18
- background: linear-gradient(180deg, #0f1a2e 0%, #0b1120 100%);
19
- padding: 0 0 6px 0;
16
+ width: 480px;
17
+ background: #0a0a12;
18
+ padding-bottom: 12px;
19
+ overflow: hidden;
20
+ position: relative;
21
+ }
22
+
23
+ /* Grid background */
24
+ .grid-bg {
25
+ position: absolute;
26
+ inset: 0;
27
+ background-image:
28
+ linear-gradient(rgba(0, 200, 255, 0.03) 1px, transparent 1px),
29
+ linear-gradient(90deg, rgba(0, 200, 255, 0.03) 1px, transparent 1px);
30
+ background-size: 24px 24px;
31
+ pointer-events: none;
32
+ z-index: 0;
33
+ }
34
+
35
+ /* Scan line effect */
36
+ .scan-line {
37
+ position: absolute;
38
+ top: 0;
39
+ left: 0;
40
+ right: 0;
41
+ height: 2px;
42
+ background: linear-gradient(90deg, transparent, rgba(0, 200, 255, 0.15), transparent);
43
+ animation: scan 4s linear infinite;
44
+ pointer-events: none;
45
+ z-index: 1;
20
46
  }
21
47
 
22
- /* Header */
48
+ @keyframes scan {
49
+ 0% { top: 0; opacity: 0; }
50
+ 10% { opacity: 1; }
51
+ 90% { opacity: 1; }
52
+ 100% { top: 100%; opacity: 0; }
53
+ }
54
+
55
+ /* ===== HEADER ===== */
23
56
  .header {
24
- height: 42px;
25
- background-color: rgba(0, 0, 0, 0.3);
57
+ padding: 20px 20px 14px;
26
58
  display: flex;
27
59
  align-items: center;
28
- justify-content: center;
29
- box-shadow: 0 2px 12px rgba(0, 0, 0, 0.4);
60
+ gap: 10px;
61
+ position: relative;
62
+ z-index: 2;
63
+ background: linear-gradient(180deg, rgba(0, 200, 255, 0.06) 0%, transparent 100%);
64
+ border-bottom: 1px solid rgba(0, 200, 255, 0.08);
30
65
  }
66
+
67
+ .header-icon {
68
+ font-size: 22px;
69
+ filter: drop-shadow(0 0 6px rgba(0, 200, 255, 0.5));
70
+ }
71
+
31
72
  .header .title {
32
- color: #8bafff;
33
- font-size: 14px;
34
- font-weight: 600;
73
+ font-size: 16px;
74
+ font-weight: 800;
75
+ color: #e0f0ff;
76
+ letter-spacing: 2px;
77
+ text-transform: uppercase;
78
+ }
79
+
80
+ .header .title span {
81
+ color: #00c8ff;
82
+ text-shadow: 0 0 10px rgba(0, 200, 255, 0.4);
83
+ }
84
+
85
+ .header-stats {
86
+ margin-left: auto;
87
+ display: flex;
88
+ gap: 12px;
89
+ }
90
+
91
+ .header-stat {
92
+ text-align: right;
93
+ }
94
+
95
+ .header-stat-value {
96
+ font-size: 16px;
97
+ font-weight: 800;
98
+ color: #00c8ff;
99
+ text-shadow: 0 0 8px rgba(0, 200, 255, 0.3);
100
+ }
101
+
102
+ .header-stat-label {
103
+ font-size: 9px;
104
+ color: #445;
35
105
  letter-spacing: 1px;
106
+ text-transform: uppercase;
36
107
  }
37
108
 
38
- /* Server list */
109
+ /* ===== SERVER LIST ===== */
39
110
  .server-list {
40
- padding: 6px 10px 0 10px;
111
+ padding: 10px 14px 0;
41
112
  display: flex;
42
113
  flex-direction: column;
43
- gap: 5px;
114
+ gap: 6px;
115
+ position: relative;
116
+ z-index: 2;
44
117
  }
45
118
 
46
- /* Server card */
119
+ /* ===== SERVER CARD ===== */
47
120
  .server-card {
48
- background: rgba(22, 33, 55, 0.8);
49
- border-radius: 6px;
50
- border: 1px solid rgba(139, 175, 255, 0.08);
121
+ background: linear-gradient(135deg, rgba(0, 200, 255, 0.04) 0%, rgba(0, 200, 255, 0.01) 100%);
122
+ border: 1px solid rgba(0, 200, 255, 0.1);
123
+ border-radius: 10px;
51
124
  overflow: hidden;
125
+ position: relative;
126
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(0, 200, 255, 0.05);
127
+ }
128
+
129
+ .server-card::before {
130
+ content: '';
131
+ position: absolute;
132
+ top: 0;
133
+ left: 0;
134
+ right: 0;
135
+ height: 1px;
136
+ background: linear-gradient(90deg, transparent 10%, rgba(0, 200, 255, 0.15) 50%, transparent 90%);
137
+ }
138
+
139
+ .server-card.offline {
140
+ opacity: 0.4;
141
+ border-color: rgba(255, 255, 255, 0.04);
142
+ background: rgba(255, 255, 255, 0.01);
52
143
  }
53
144
 
54
- /* Card info */
55
145
  .server-info {
56
- padding: 7px 12px 5px 12px;
146
+ padding: 10px 14px 6px;
57
147
  }
148
+
58
149
  .server-row {
59
150
  display: flex;
60
151
  align-items: center;
61
152
  justify-content: space-between;
62
153
  }
154
+
63
155
  .server-left {
64
156
  display: flex;
65
157
  align-items: center;
66
- gap: 6px;
158
+ gap: 8px;
67
159
  flex: 1;
68
160
  min-width: 0;
69
161
  }
70
- .status-indicator {
71
- width: 6px;
72
- height: 6px;
162
+
163
+ .status-dot {
164
+ width: 8px;
165
+ height: 8px;
73
166
  border-radius: 50%;
74
167
  flex-shrink: 0;
168
+ position: relative;
169
+ }
170
+
171
+ .status-dot.online {
172
+ background: #00ff88;
173
+ box-shadow: 0 0 6px rgba(0, 255, 136, 0.6), 0 0 12px rgba(0, 255, 136, 0.2);
75
174
  }
76
- .status-indicator.online {
77
- background-color: #34d058;
78
- box-shadow: 0 0 5px rgba(52, 208, 88, 0.5);
175
+
176
+ .status-dot.online::after {
177
+ content: '';
178
+ position: absolute;
179
+ inset: -3px;
180
+ border-radius: 50%;
181
+ border: 1px solid rgba(0, 255, 136, 0.2);
182
+ animation: pulse 2s ease-out infinite;
183
+ }
184
+
185
+ @keyframes pulse {
186
+ 0% { transform: scale(0.8); opacity: 1; }
187
+ 100% { transform: scale(1.8); opacity: 0; }
79
188
  }
80
- .status-indicator.offline {
81
- background-color: #484f58;
189
+
190
+ .status-dot.offline {
191
+ background: #333;
82
192
  }
193
+
83
194
  .server-name {
84
- color: #e6edf3;
195
+ color: #d0e8ff;
85
196
  font-size: 13px;
86
- font-weight: 600;
197
+ font-weight: 700;
87
198
  white-space: nowrap;
88
199
  overflow: hidden;
89
200
  text-overflow: ellipsis;
90
- max-width: 200px;
201
+ letter-spacing: 0.3px;
91
202
  }
92
- .player-count {
93
- color: #6e7a8a;
94
- font-size: 11px;
95
- white-space: nowrap;
203
+
204
+ .player-info {
205
+ display: flex;
206
+ align-items: baseline;
207
+ gap: 4px;
96
208
  flex-shrink: 0;
97
- margin-left: 6px;
209
+ margin-left: 10px;
98
210
  }
99
- .player-count .num {
100
- color: #c9d1d9;
101
- font-weight: 700;
102
- font-size: 12px;
211
+
212
+ .player-num {
213
+ font-size: 16px;
214
+ font-weight: 900;
215
+ color: #00c8ff;
216
+ text-shadow: 0 0 8px rgba(0, 200, 255, 0.3);
217
+ font-variant-numeric: tabular-nums;
218
+ }
219
+
220
+ .player-max {
221
+ font-size: 11px;
222
+ color: #445;
223
+ font-weight: 500;
103
224
  }
104
225
 
105
- /* Features row */
226
+ /* Progress bar */
227
+ .server-bar {
228
+ height: 2px;
229
+ background: rgba(0, 200, 255, 0.06);
230
+ border-radius: 1px;
231
+ margin-top: 6px;
232
+ overflow: hidden;
233
+ }
234
+
235
+ .server-bar-fill {
236
+ height: 100%;
237
+ border-radius: 1px;
238
+ background: linear-gradient(90deg, #00c8ff, #00ff88);
239
+ box-shadow: 0 0 6px rgba(0, 200, 255, 0.3);
240
+ transition: width 0.3s;
241
+ }
242
+
243
+ /* Features */
106
244
  .features {
107
245
  display: flex;
108
246
  align-items: center;
109
247
  gap: 8px;
110
- margin-top: 3px;
248
+ margin-top: 5px;
111
249
  }
250
+
112
251
  .feature {
113
- font-size: 10px;
114
- color: #5a6572;
252
+ font-size: 9px;
253
+ color: #3a4a5a;
254
+ padding: 1px 6px;
255
+ border-radius: 4px;
256
+ border: 1px solid rgba(255, 255, 255, 0.04);
257
+ background: rgba(255, 255, 255, 0.02);
115
258
  white-space: nowrap;
259
+ letter-spacing: 0.3px;
116
260
  }
117
261
 
118
- /* Queue badge */
119
262
  .queue-badge {
120
- color: #f0883e;
121
263
  font-size: 10px;
264
+ color: #ff8844;
122
265
  margin-left: 6px;
266
+ font-weight: 600;
123
267
  }
124
268
 
125
- /* Chart area */
269
+ /* Chart */
126
270
  .chart-area {
127
271
  position: relative;
128
- height: 38px;
272
+ height: 36px;
273
+ opacity: 0.8;
129
274
  }
275
+
130
276
  .chart-area svg {
131
277
  display: block;
132
278
  width: 100%;
133
279
  height: 100%;
134
280
  }
135
281
 
282
+ /* ===== FOOTER ===== */
283
+ .footer {
284
+ text-align: center;
285
+ padding: 10px 20px 0;
286
+ position: relative;
287
+ z-index: 2;
288
+ }
289
+
290
+ .footer-line {
291
+ height: 1px;
292
+ background: linear-gradient(90deg, transparent, rgba(0, 200, 255, 0.15), transparent);
293
+ margin-bottom: 8px;
294
+ }
295
+
296
+ .footer-text {
297
+ font-size: 9px;
298
+ color: #223;
299
+ letter-spacing: 1px;
300
+ }
136
301
  </style>
137
302
  </head>
138
303
  <body>
139
304
  <div id="container">
305
+ <div class="grid-bg"></div>
306
+ <div class="scan-line"></div>
307
+
140
308
  <div class="header">
141
- <div class="title">TruckersMP 服务器状态</div>
309
+ <span class="header-icon">📡</span>
310
+ <div class="title">TRUCKERS<span>MP</span> 服务器</div>
311
+ <div class="header-stats">
312
+ <div class="header-stat">
313
+ <div class="header-stat-value" id="total-online">-</div>
314
+ <div class="header-stat-label">在线</div>
315
+ </div>
316
+ </div>
142
317
  </div>
143
318
 
144
319
  <div class="server-list" id="server-list"></div>
145
320
 
321
+ <div class="footer">
322
+ <div class="footer-line"></div>
323
+ <div class="footer-text">REAL-TIME SERVER MONITOR</div>
324
+ </div>
146
325
  </div>
147
326
 
148
327
  <script>
149
- /**
150
- * 构建 SVG 面积折线图
151
- * maxPlayer 的 50% 作为余量,上限不超过 maxPlayer
152
- */
153
328
  function buildChartSVG(data, width, height, maxPlayer) {
154
- const padTop = 2;
155
- const padBottom = 0;
156
- const chartW = width;
157
- const chartH = height - padTop - padBottom;
158
-
329
+ const padTop = 2, padBottom = 0;
330
+ const chartW = width, chartH = height - padTop - padBottom;
159
331
  const dataMax = Math.max(...data, 1);
160
332
  const max = Math.min(maxPlayer, maxPlayer * 0.12 + dataMax);
161
333
  const step = chartW / (data.length - 1);
@@ -167,9 +339,7 @@ function buildChartSVG(data, width, height, maxPlayer) {
167
339
  });
168
340
 
169
341
  const lineD = points.map((p, i) => (i === 0 ? `M${p[0]},${p[1]}` : `L${p[0]},${p[1]}`)).join(' ');
170
- const areaD = lineD
171
- + ` L${points[points.length - 1][0]},${padTop + chartH}`
172
- + ` L${points[0][0]},${padTop + chartH} Z`;
342
+ const areaD = lineD + ` L${points[points.length - 1][0]},${padTop + chartH} L${points[0][0]},${padTop + chartH} Z`;
173
343
 
174
344
  const gid = 'g' + Math.random().toString(36).slice(2, 8);
175
345
 
@@ -177,27 +347,19 @@ function buildChartSVG(data, width, height, maxPlayer) {
177
347
  <svg viewBox="0 0 ${width} ${height}" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
178
348
  <defs>
179
349
  <linearGradient id="${gid}" x1="0" y1="0" x2="0" y2="1">
180
- <stop offset="0%" stop-color="rgba(79,139,255,0.4)"/>
181
- <stop offset="100%" stop-color="rgba(79,139,255,0.02)"/>
350
+ <stop offset="0%" stop-color="rgba(0,200,255,0.3)"/>
351
+ <stop offset="100%" stop-color="rgba(0,200,255,0.01)"/>
182
352
  </linearGradient>
183
353
  </defs>
184
354
  <path d="${areaD}" fill="url(#${gid})"/>
185
- <path d="${lineD}" fill="none" stroke="#4f8bff" stroke-width="1.5" stroke-linejoin="round"/>
355
+ <path d="${lineD}" fill="none" stroke="#00c8ff" stroke-width="1.5" stroke-linejoin="round"/>
186
356
  </svg>`;
187
357
  }
188
358
 
189
- /**
190
- * 将 playerHistory 标准化为 288 个点(24h,每5分钟一个点)
191
- * 缺失的时间段填充为 0
192
- * @param {Array<{updateTime: string, playerCount: number}>} playerHistory
193
- */
194
359
  function normalizeHistory(playerHistory) {
195
- const SLOT_COUNT = 288;
196
- const SLOT_MS = 300000;
197
-
360
+ const SLOT_COUNT = 288, SLOT_MS = 300000;
198
361
  if (!playerHistory || playerHistory.length === 0) return [];
199
362
 
200
- // API 返回的是北京时间(UTC+8),用 Date.UTC 生成时间戳再减去8小时偏移
201
363
  function parseTime(str) {
202
364
  const [datePart, timePart] = str.trim().replace('T', ' ').split(' ');
203
365
  const [y, m, d] = datePart.split('-').map(Number);
@@ -208,7 +370,6 @@ function normalizeHistory(playerHistory) {
208
370
 
209
371
  const now = Date.now();
210
372
  const currentSlot = Math.floor(now / SLOT_MS) * SLOT_MS;
211
-
212
373
  const dataMap = {};
213
374
  for (const item of playerHistory) {
214
375
  const ts = parseTime(item.updateTime);
@@ -222,54 +383,46 @@ function normalizeHistory(playerHistory) {
222
383
  const slotTime = currentSlot - (SLOT_COUNT - 1 - i) * SLOT_MS;
223
384
  result.push(dataMap[slotTime] !== undefined ? dataMap[slotTime] : 0);
224
385
  }
225
-
226
386
  return result;
227
387
  }
228
388
 
229
- /**
230
- * 渲染一个服务器卡片
231
- */
232
389
  function createServerCard(server) {
233
390
  const card = document.createElement('div');
234
- card.className = 'server-card';
391
+ card.className = `server-card${server.isOnline !== 1 ? ' offline' : ''}`;
392
+
393
+ const isOnline = server.isOnline === 1;
394
+ const pct = maxP => Math.min(100, (server.playerCount / maxP) * 100);
235
395
 
236
- // 只展示存在的特性
237
396
  let featuresHTML = '';
238
- if (server.collisionsEnable === 1) {
239
- featuresHTML += '<span class="feature">💥 碰撞</span>';
240
- }
241
- if (server.afkEnable === 0) {
242
- featuresHTML += '<span class="feature">💤 挂机</span>';
243
- }
244
- if (server.policeCarEnable === 1) {
245
- featuresHTML += '<span class="feature">🚓 警车</span>';
246
- }
247
- if (server.speedLimiterEnable === 1) {
248
- featuresHTML += '<span class="feature">🐢 限速</span>';
249
- }
397
+ if (server.collisionsEnable === 1) featuresHTML += '<span class="feature">💥 碰撞</span>';
398
+ if (server.afkEnable === 0) featuresHTML += '<span class="feature">💤 挂机</span>';
399
+ if (server.policeCarEnable === 1) featuresHTML += '<span class="feature">🚓 警车</span>';
400
+ if (server.speedLimiterEnable === 1) featuresHTML += '<span class="feature">🐢 限速</span>';
250
401
 
251
- // 队列
252
402
  let queueText = '';
253
- if (server.queueCount > 0) {
254
- queueText = `<span class="queue-badge">队列 ${server.queueCount}</span>`;
255
- }
403
+ if (server.queueCount > 0) queueText = `<span class="queue-badge">⏳ ${server.queueCount}</span>`;
256
404
 
257
405
  card.innerHTML = `
258
406
  <div class="server-info">
259
407
  <div class="server-row">
260
408
  <div class="server-left">
261
- <span class="status-indicator ${server.isOnline === 1 ? 'online' : 'offline'}"></span>
409
+ <div class="status-dot ${isOnline ? 'online' : 'offline'}"></div>
262
410
  <span class="server-name">${server.serverName}</span>
263
411
  </div>
264
- <span class="player-count"><span class="num">${server.playerCount}</span> / ${server.maxPlayer}${queueText}</span>
412
+ <div class="player-info">
413
+ <span class="player-num">${server.playerCount}</span>
414
+ <span class="player-max">/ ${server.maxPlayer}</span>
415
+ ${queueText}
416
+ </div>
265
417
  </div>
418
+ <div class="server-bar"><div class="server-bar-fill" style="width:${pct(server.maxPlayer)}%"></div></div>
266
419
  ${featuresHTML ? '<div class="features">' + featuresHTML + '</div>' : ''}
267
420
  </div>
268
421
  <div class="chart-area">
269
- ${server.isOnline === 1 && server.playerHistory && server.playerHistory.length > 0
422
+ ${isOnline && server.playerHistory && server.playerHistory.length > 0
270
423
  ? (() => {
271
424
  const normalized = normalizeHistory(server.playerHistory);
272
- return normalized.length > 0 ? buildChartSVG(normalized, 380, 38, server.maxPlayer) : '';
425
+ return normalized.length > 0 ? buildChartSVG(normalized, 480, 36, server.maxPlayer) : '';
273
426
  })()
274
427
  : ''}
275
428
  </div>`;
@@ -277,22 +430,21 @@ function createServerCard(server) {
277
430
  return card;
278
431
  }
279
432
 
280
- /**
281
- * 主渲染函数
282
- */
283
433
  function setData(apiData) {
284
434
  if (!apiData || !apiData.data) return;
285
435
 
286
436
  const servers = apiData.data;
287
437
  const listEl = document.getElementById('server-list');
288
-
289
438
  listEl.innerHTML = '';
290
439
 
440
+ // Total online count
441
+ const totalOnline = servers.reduce((sum, s) => sum + (s.playerCount || 0), 0);
442
+ document.getElementById('total-online').textContent = totalOnline.toLocaleString();
443
+
291
444
  servers.forEach(server => {
292
445
  listEl.appendChild(createServerCard(server));
293
446
  });
294
447
  }
295
-
296
448
  </script>
297
449
  </body>
298
450
  </html>