dsh-peak-hours 1.10.1 → 1.11.0

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.
Files changed (4) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +19 -3
  3. package/client.js +174 -25
  4. package/package.json +13 -3
package/CHANGELOG.md ADDED
@@ -0,0 +1,34 @@
1
+ # 更新日志
2
+
3
+ ## 1.11.0 — 2026-10-29
4
+
5
+ ### 修正
6
+
7
+ - **说明文案与实现对齐**:日程条的说明原来写「红色=高峰、绿色=空闲」,但条子实际是琥珀橙 / DeepSeek 蓝,现已改为「橙色=高峰、蓝色=空闲」(英文同步为 `amber` / `blue`)。
8
+ - 节假日说明不再暴露源码里的 `HOLIDAYS` 常量名,改成数据驱动:从已收录年份自动生成「节假日表已收录 2026 年中秋、国庆」。
9
+ - `package.json` 补充 `./package.json` 导出、`engines`、`scripts.test`,发布物更规范。
10
+
11
+ ### 新增
12
+
13
+ - **点击复制状态**:点一下角标(或聚焦后按 Enter / 空格)即可把「高峰 ×2 · 3小时11分后转空闲(北京时间)」复制到剪贴板,1.5 秒内显示「已复制」。剪贴板不可用时回退到临时 textarea。
14
+ - **切换前 10 分钟强调**:距峰↔谷切换不足 10 分钟时,倒计时变琥珀色加粗,圆点轻微脉冲(`prefers-reduced-motion` 下自动关闭动画)。
15
+ - **更完整的 tooltip**:新增「明天」预告(明天周末 / 法定节假日 / 工作日),以及点击复制的提示。
16
+ - **可访问性**:角标改为 `role="button"` + `tabIndex`,带 `aria-label`(含状态、倒计时与操作提示),键盘 `:focus-visible` 有可见焦点环。
17
+ - **单元测试与 CI**:`npm test` 用 `node --test` 跑 15 个用例(时段边界 09:00/12:00/14:00/18:00、周末、节假日、跨日倒计时、日程条分段与宽度、游标位置、折叠态、复制、中英文 key 对齐),GitHub Actions 在 22.x 上跑测试与打包检查。
18
+
19
+ ## 1.10.1 — 2026-10-28
20
+
21
+ - 修正 bundle patch 里的行名仍然指向改名前的 `@local/dsh-peak-hours`,导致重命名后客户端半不装载、角标消失。
22
+
23
+ ## 1.10.0 — 2026-10-28
24
+
25
+ - 收窄空闲色渐变(`#4d6bfe → #6383ff`),让条子底色与侧栏 logo 的品牌蓝一致。
26
+ - 发布物整理:包名改为 `dsh-peak-hours`,补 LICENSE / README / 图标 / 截图 / `screenshots.json`。
27
+
28
+ ## 1.9.0 — 2026-10-27
29
+
30
+ - 空闲色从绿色换成 DeepSeek 品牌蓝 `#4d6bfe`;高峰仍为琥珀色 `#f59e0b`。
31
+
32
+ ## 1.4.0 — 2026-10-27
33
+
34
+ - 进度条改为「当天 00:00–24:00 的日程条」:红/绿分段 + 白色当前时刻标尺。
package/README.md CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  ![侧栏角标](screenshots/screenshot-1.png)
6
6
 
7
+ ![ci](https://github.com/run666888/dsh-peak-hours/actions/workflows/ci.yml/badge.svg)
8
+
7
9
  ## 它显示什么
8
10
 
9
11
  挂在侧栏底部动作行(`sidebar.footer.action`,「插件市场」下面),一行搞定三件事:
@@ -14,7 +16,12 @@
14
16
 
15
17
  侧栏折叠成窄轨时自动退化为「圆点 + 一小截进度条」,不占地方。
16
18
 
17
- 鼠标悬停能看到完整说明:当前段起止时间、下一段是什么、以及高峰/空闲的价格表。
19
+ **距切换不到 10 分钟**时,倒计时会变成琥珀色加粗、圆点轻微脉冲,提醒你「要涨价了」或「要降价了」(系统开启减少动态效果时动画自动关闭)。
20
+
21
+ **点一下角标**(或聚焦后按 Enter / 空格)会把当前状态复制到剪贴板,例如
22
+ `DeepSeek 高峰 ×2 · 3小时11分后转空闲(北京时间)`,方便贴给同事。
23
+
24
+ 鼠标悬停能看到完整说明:当前段起止时间、明天是工作日还是休息、以及高峰/空闲的价格表。
18
25
 
19
26
  ## 计费规则
20
27
 
@@ -25,13 +32,13 @@
25
32
  - **整天空闲**:周六、周日,以及中国的法定节假日
26
33
  - 闲时价 = 高峰价的一半
27
34
 
28
- 节假日表是**内置的,不联网**:目前覆盖 2026 年(中秋 9/25–27、国庆 10/1–7)。2026 年 9/20(周日)和 10/10(周六)是调休上班日,按规则仍算空闲。表里没有的年份退化成「周末空闲」规则——这不是想当然,是保守起见的选择,遇到新的节假日安排需要更新 `client.js` 里的 `HOLIDAYS`。
35
+ 节假日表是**内置的,不联网**:目前覆盖 2026 年(中秋 9/25–27、国庆 10/1–7)。2026 年 9/20(周日)和 10/10(周六)是调休上班日,按规则仍算空闲。表里没有的年份退化成「周末空闲」规则——这是保守选择:宁可把某天当成空闲(少标高峰),也不误报高峰;遇到新的节假日安排需要更新 `client.js` 里的 `HOLIDAYS`,tooltip 里显示的年份由该表自动推导。
29
36
 
30
37
  时间一律按北京时间(UTC+8)算,和你浏览器的时区无关。
31
38
 
32
39
  ## 安装
33
40
 
34
- 从 npm(已发布后):
41
+ 从 npm:
35
42
 
36
43
  ```sh
37
44
  dsh plugin --profile web add dsh-peak-hours
@@ -57,6 +64,15 @@ dsh plugin --profile web remove dsh-peak-hours
57
64
  - 纯客户端插件:不注册 host 工具、不写文件、不发网络请求、不需要任何配置或凭据
58
65
  - 颜色是固定值(高峰 `#f59e0b`、空闲 `#4d6bfe`,即 DeepSeek 品牌蓝),不跟随主题 `brand-primary`,以免换皮肤后蓝色被改成别的色
59
66
 
67
+ ## 开发
68
+
69
+ ```sh
70
+ npm test # 15 个单元测试(node --test,零依赖)
71
+ npm run verify # npm pack --dry-run,检查发布清单
72
+ ```
73
+
74
+ 测试通过 `vm` 加载 `client.js`,用最小的 React / locale / document 替身渲染组件,因此不用起 GUI 也能验证时段判定、分段宽度、折叠态与复制逻辑。
75
+
60
76
  ## 许可
61
77
 
62
78
  MIT
package/client.js CHANGED
@@ -14,11 +14,14 @@ window.__ModuleLoader__.load({
14
14
  [14 * 60, 18 * 60],
15
15
  ];
16
16
 
17
+ // 距峰↔谷切换不足该分钟数时,倒计时与圆点进入强调态。
18
+ const SOON_MINUTES = 10;
19
+
17
20
  /**
18
21
  * 中国法定节假日(全天空闲计价)。来源:2026-09-19 官方说明
19
22
  * 「法定节假日全天、调休上班的周末全天均按空闲时段计费」。
20
- * 目前核对过 2026 年中秋、国庆;往后年份请按官方公告补充,
21
- * 未覆盖的年份只按周末规则判定。
23
+ * 已核对 2026 年中秋、国庆;新一年的放假安排公布后按公告补充,
24
+ * 未收录的年份只按周末规则判定(周末本就全天空闲,因此不会判错方向)。
22
25
  */
23
26
  const HOLIDAYS = {
24
27
  2026: [
@@ -27,6 +30,7 @@ window.__ModuleLoader__.load({
27
30
  '2026-10-05', '2026-10-06', '2026-10-07',
28
31
  ],
29
32
  };
33
+ const HOLIDAY_YEARS = Object.keys(HOLIDAYS).sort();
30
34
  const holidayCache = new Map();
31
35
  function holidaySet(year) {
32
36
  if (holidayCache.has(year)) return holidayCache.get(year);
@@ -73,6 +77,15 @@ window.__ModuleLoader__.load({
73
77
  };
74
78
  }
75
79
 
80
+ /** 次日的计价形态:holiday / weekend / workday。 */
81
+ function kindAt(ms) {
82
+ const t = beijing(ms);
83
+ const holidays = holidaySet(t.year);
84
+ if (holidays && holidays.has(t.key)) return 'holiday';
85
+ if (t.dow === 0 || t.dow === 6) return 'weekend';
86
+ return 'workday';
87
+ }
88
+
76
89
  /** 下一个峰↔谷切换的瞬间(分钟对齐),最多向前找 14 天。 */
77
90
  function nextSwitch(ms) {
78
91
  const current = stateAt(ms);
@@ -107,7 +120,7 @@ window.__ModuleLoader__.load({
107
120
  return { start, end, progress };
108
121
  }
109
122
 
110
- /** 当天 00:00–24:00 的峰谷分段(分钟),画成红绿相间的日程条。 */
123
+ /** 当天 00:00–24:00 的峰谷分段(分钟),画成橙蓝相间的日程条。 */
111
124
  function daySchedule(ms) {
112
125
  const st = stateAt(ms);
113
126
  if (st.allDayOff) return [{ from: 0, to: 1440, peak: false }];
@@ -142,6 +155,10 @@ window.__ModuleLoader__.load({
142
155
  return b.month + '-' + pad2(b.day) + ' ' + b.clock;
143
156
  }
144
157
 
158
+ function holidayNote(t) {
159
+ return (t('tip.table') || '').replace('{years}', HOLIDAY_YEARS.join(' / '));
160
+ }
161
+
145
162
  function tooltip(t, st, phase, now) {
146
163
  const lines = [
147
164
  t('tip.head'),
@@ -153,6 +170,14 @@ window.__ModuleLoader__.load({
153
170
  ];
154
171
  if (st.isHoliday) lines.push(t('tip.holidayToday'));
155
172
  else if (st.isWeekend) lines.push(t('tip.weekendToday'));
173
+ const tomorrow = kindAt(now + 1440 * MINUTE);
174
+ lines.push(
175
+ tomorrow === 'holiday'
176
+ ? t('tip.tomorrowHoliday')
177
+ : tomorrow === 'weekend'
178
+ ? t('tip.tomorrowWeekend')
179
+ : t('tip.tomorrowWorkday'),
180
+ );
156
181
  if (phase.start !== null && phase.end !== null) {
157
182
  lines.push(
158
183
  t('tip.phase') + clockLabel(phase.start) + ' → ' + clockLabel(phase.end),
@@ -169,12 +194,70 @@ window.__ModuleLoader__.load({
169
194
  t('tip.flash'),
170
195
  t('tip.pro'),
171
196
  '',
172
- t('tip.table'),
197
+ holidayNote(t),
173
198
  t('tip.tz'),
199
+ t('tip.hint'),
174
200
  );
175
201
  return lines.join('\n');
176
202
  }
177
203
 
204
+ function copySummary(t, st, phase, now) {
205
+ const pill = st.peak ? t('price.multiplier') : t('price.discount');
206
+ const rest =
207
+ phase.end !== null
208
+ ? t('tip.next') +
209
+ fmtDuration(phase.end - now, t) +
210
+ (st.peak ? t('next.off') : t('next.peak'))
211
+ : '';
212
+ return (
213
+ 'DeepSeek ' +
214
+ (st.peak ? t('status.peak') : t('status.off')) +
215
+ ' ' +
216
+ pill +
217
+ ' · ' +
218
+ rest +
219
+ ' ' +
220
+ t('copy.suffix')
221
+ );
222
+ }
223
+
224
+ /** 复制到剪贴板:Clipboard API 优先,失败退回临时 textarea。 */
225
+ function writeClipboard(text) {
226
+ try {
227
+ if (
228
+ typeof navigator !== 'undefined' &&
229
+ navigator.clipboard &&
230
+ typeof navigator.clipboard.writeText === 'function'
231
+ ) {
232
+ return Promise.resolve(navigator.clipboard.writeText(text)).then(
233
+ () => true,
234
+ () => fallbackCopy(text),
235
+ );
236
+ }
237
+ } catch {
238
+ /* 落到兜底 */
239
+ }
240
+ return Promise.resolve(fallbackCopy(text));
241
+ }
242
+
243
+ function fallbackCopy(text) {
244
+ try {
245
+ if (typeof document === 'undefined' || !document.body) return false;
246
+ const area = document.createElement('textarea');
247
+ area.value = text;
248
+ area.setAttribute('readonly', '');
249
+ area.style.position = 'fixed';
250
+ area.style.opacity = '0';
251
+ document.body.appendChild(area);
252
+ area.select();
253
+ const ok = document.execCommand && document.execCommand('copy');
254
+ document.body.removeChild(area);
255
+ return !!ok;
256
+ } catch {
257
+ return false;
258
+ }
259
+ }
260
+
178
261
  // 字族跟随宿主,字号显式压到侧栏次级字号(0.8125rem ≈ 13px),
179
262
  // 因为 footer 动作行从根节点继承的是 1rem,直接用 inherit 会比菜单项大一号。
180
263
  const CSS = `
@@ -189,10 +272,14 @@ window.__ModuleLoader__.load({
189
272
  font-family: inherit; font-size: 14px; font-size: .875rem;
190
273
  font-weight: 400; line-height: 1.45;
191
274
  color: var(--dsw-alias-label-primary);
192
- user-select: none; cursor: default;
275
+ user-select: none; cursor: pointer; outline: none;
193
276
  transition: background-color .15s ease;
194
277
  }
195
278
  .dshph-root:hover { background: var(--dsw-alias-bg-layer-2); }
279
+ .dshph-root:focus-visible {
280
+ outline: 2px solid var(--dsw-alias-brand-primary, #4d6bfe);
281
+ outline-offset: 1px;
282
+ }
196
283
  .dshph-head { display: flex; align-items: center; gap: 6px; min-width: 0; }
197
284
  .dshph-dot { flex: none; width: 7px; height: 7px; border-radius: 50%; }
198
285
  .dshph-peak .dshph-dot {
@@ -203,6 +290,11 @@ window.__ModuleLoader__.load({
203
290
  background: var(--dshph-off);
204
291
  box-shadow: 0 0 0 3px rgba(77, 107, 254, .2);
205
292
  }
293
+ .dshph-dot-soon { animation: dshph-pulse 1.6s ease-in-out infinite; }
294
+ @keyframes dshph-pulse {
295
+ 0%, 100% { box-shadow: 0 0 0 2px rgba(245, 158, 11, .22); }
296
+ 50% { box-shadow: 0 0 0 5px rgba(245, 158, 11, .06); }
297
+ }
206
298
  .dshph-label,
207
299
  .dshph-pill,
208
300
  .dshph-next {
@@ -216,6 +308,8 @@ window.__ModuleLoader__.load({
216
308
  flex: 0 1 auto; min-width: 0; overflow: hidden;
217
309
  text-overflow: ellipsis; white-space: nowrap;
218
310
  }
311
+ .dshph-soon .dshph-next { color: #f59e0b; font-weight: 600; }
312
+ .dshph-toast { color: var(--dsw-alias-label-secondary); }
219
313
  .dshph-track {
220
314
  position: relative; height: 8px; border-radius: 999px;
221
315
  background: var(--dsw-alias-bg-layer-2); overflow: hidden;
@@ -240,6 +334,11 @@ window.__ModuleLoader__.load({
240
334
  }
241
335
  .dshph-rail .dshph-track { width: 26px; height: 6px; }
242
336
  .dshph-rail .dshph-cursor { width: 1.5px; }
337
+ @media (prefers-reduced-motion: reduce) {
338
+ .dshph-root, .dshph-root * {
339
+ animation: none !important; transition: none !important;
340
+ }
341
+ }
243
342
  `;
244
343
 
245
344
  function ensureStyle() {
@@ -270,18 +369,25 @@ window.__ModuleLoader__.load({
270
369
  'tip.head': 'DeepSeek 峰谷计费',
271
370
  'tip.current': '当前:',
272
371
  'tip.window': '高峰时段:工作日 09:00–12:00、14:00–18:00(北京时间)',
273
- 'tip.bar': '日程条:当天 00:00–24:00,红色=高峰、绿色=空闲,白线是当前时刻',
372
+ 'tip.bar': '日程条:当天 00:00–24:00,橙色=高峰、蓝色=空闲,白线是当前时刻',
274
373
  'tip.rule': '其余时间按空闲计价,闲时价为高峰价的一半',
275
374
  'tip.weekend': '周末与法定节假日全天按空闲计价',
276
375
  'tip.holidayToday': '今天:法定节假日,全天按空闲计价',
277
376
  'tip.weekendToday': '今天:周末,全天按空闲计价',
377
+ 'tip.tomorrowHoliday': '明天:法定节假日,全天按空闲计价',
378
+ 'tip.tomorrowWeekend': '明天:周末,全天按空闲计价',
379
+ 'tip.tomorrowWorkday': '明天:工作日,恢复按 09:00–12:00、14:00–18:00 计价',
278
380
  'tip.phase': '本段:',
279
381
  'tip.next': '距切换:',
280
382
  'tip.prices': '价格(元/百万 tokens,空闲 → 高峰)',
281
383
  'tip.flash': 'deepseek-flash:缓存命中 0.02 → 0.04|未命中 1 → 2|输出 4 → 8',
282
384
  'tip.pro': 'deepseek-v4-pro:缓存命中 0.15 → 0.30|未命中 4.5 → 9.0|输出 13.5 → 27.0',
283
- 'tip.table': '节假日表当前覆盖 2026 年中秋、国庆;其他年份请在源码 HOLIDAYS 中补充',
385
+ 'tip.table': '节假日表已收录 {years} 年中秋、国庆;新一年安排公布后随版本更新',
284
386
  'tip.tz': '判定时区:北京时间(UTC+8)',
387
+ 'tip.hint': '点击复制当前状态',
388
+ 'copy.ok': '已复制',
389
+ 'copy.fail': '复制失败',
390
+ 'copy.suffix': '(北京时间)',
285
391
  };
286
392
 
287
393
  const EN = {
@@ -299,18 +405,25 @@ window.__ModuleLoader__.load({
299
405
  'tip.head': 'DeepSeek peak/off-peak pricing',
300
406
  'tip.current': 'Now: ',
301
407
  'tip.window': 'Peak: weekdays 09:00–12:00 and 14:00–18:00 (Beijing time)',
302
- 'tip.bar': 'Day bar: 00:00–24:00, red = peak, green = off-peak, white line = now',
408
+ 'tip.bar': 'Day bar: 00:00–24:00, amber = peak, blue = off-peak, white line = now',
303
409
  'tip.rule': 'Everything else is off-peak, priced at half of the peak rate',
304
410
  'tip.weekend': 'Weekends and public holidays are off-peak all day',
305
411
  'tip.holidayToday': 'Today: public holiday, off-peak all day',
306
412
  'tip.weekendToday': 'Today: weekend, off-peak all day',
413
+ 'tip.tomorrowHoliday': 'Tomorrow: public holiday, off-peak all day',
414
+ 'tip.tomorrowWeekend': 'Tomorrow: weekend, off-peak all day',
415
+ 'tip.tomorrowWorkday': 'Tomorrow: weekday, peak windows apply again',
307
416
  'tip.phase': 'This block: ',
308
417
  'tip.next': 'Switch in ',
309
418
  'tip.prices': 'Price (CNY / million tokens, off-peak → peak)',
310
419
  'tip.flash': 'deepseek-flash: cache hit 0.02 → 0.04 | miss 1 → 2 | output 4 → 8',
311
420
  'tip.pro': 'deepseek-v4-pro: cache hit 0.15 → 0.30 | miss 4.5 → 9.0 | output 13.5 → 27.0',
312
- 'tip.table': 'The holiday table currently covers 2026 Mid-Autumn and National Day; add later years to HOLIDAYS',
421
+ 'tip.table': 'Holiday table covers {years} (Mid-Autumn, National Day); updated per release',
313
422
  'tip.tz': 'Evaluated in Beijing time (UTC+8)',
423
+ 'tip.hint': 'Click to copy the current state',
424
+ 'copy.ok': 'Copied',
425
+ 'copy.fail': 'Copy failed',
426
+ 'copy.suffix': '(Beijing time)',
314
427
  };
315
428
 
316
429
  let translate = (key) => (EN[key] !== undefined ? EN[key] : key);
@@ -318,6 +431,8 @@ window.__ModuleLoader__.load({
318
431
  function PeakBadge(props) {
319
432
  const t = translate;
320
433
  const [now, setNow] = React.useState(() => Date.now());
434
+ const [toast, setToast] = React.useState('');
435
+
321
436
  React.useEffect(() => {
322
437
  const id = setInterval(() => setNow(Date.now()), 15000);
323
438
  return () => clearInterval(id);
@@ -330,12 +445,42 @@ window.__ModuleLoader__.load({
330
445
  const wide = !props || props.wide !== false;
331
446
  const stateText = off ? t('status.off') : t('status.peak');
332
447
  const title = tooltip(t, st, phase, now);
333
- const rootClass = 'dshph-root ' + (off ? 'dshph-off' : 'dshph-peak');
448
+ const soon =
449
+ phase.end !== null && phase.end - now <= SOON_MINUTES * MINUTE;
450
+ const rootClass =
451
+ 'dshph-root ' +
452
+ (off ? 'dshph-off' : 'dshph-peak') +
453
+ (soon ? ' dshph-soon' : '');
334
454
  const cursorLeft = pct(st.beijing.minute);
335
455
  const remaining = phase.end
336
456
  ? fmtDuration(phase.end - now, t) + (off ? t('next.peak') : t('next.off'))
337
457
  : '';
338
458
 
459
+ const onCopy = () => {
460
+ const text = copySummary(t, st, phase, now);
461
+ let result;
462
+ try {
463
+ result = writeClipboard(text);
464
+ } catch {
465
+ result = Promise.resolve(false);
466
+ }
467
+ Promise.resolve(result)
468
+ .then((ok) => setToast(ok ? t('copy.ok') : t('copy.fail')))
469
+ .catch(() => setToast(t('copy.fail')));
470
+ setTimeout(() => setToast(''), 1500);
471
+ };
472
+
473
+ const onKeyDown = (event) => {
474
+ if (event.key === 'Enter' || event.key === ' ' || event.key === 'Spacebar') {
475
+ event.preventDefault();
476
+ onCopy();
477
+ }
478
+ };
479
+
480
+ const headText = toast
481
+ ? h('span', { className: 'dshph-next dshph-toast' }, toast)
482
+ : h('span', { className: 'dshph-next' }, remaining);
483
+
339
484
  const track = h(
340
485
  'div',
341
486
  { className: 'dshph-track' },
@@ -350,32 +495,36 @@ window.__ModuleLoader__.load({
350
495
  h('span', { key: 'cursor', className: 'dshph-cursor', style: { left: cursorLeft } }),
351
496
  );
352
497
 
498
+ const common = {
499
+ title,
500
+ role: 'button',
501
+ tabIndex: 0,
502
+ onClick: onCopy,
503
+ onKeyDown,
504
+ 'aria-label':
505
+ t('label') + ': ' + stateText + ', ' + remaining + '. ' + t('tip.hint'),
506
+ };
507
+
353
508
  if (!wide) {
354
509
  return h(
355
510
  'div',
356
- {
357
- className: rootClass + ' dshph-rail',
358
- title,
359
- role: 'status',
360
- 'aria-label': t('label') + ': ' + stateText,
361
- },
362
- h('span', { className: 'dshph-dot' }),
511
+ { ...common, className: rootClass + ' dshph-rail' },
512
+ h('span', {
513
+ className: 'dshph-dot' + (soon ? ' dshph-dot-soon' : ''),
514
+ }),
363
515
  track,
364
516
  );
365
517
  }
366
518
 
367
519
  return h(
368
520
  'div',
369
- {
370
- className: rootClass,
371
- title,
372
- role: 'status',
373
- 'aria-label': t('label') + ': ' + stateText,
374
- },
521
+ { ...common, className: rootClass },
375
522
  h(
376
523
  'div',
377
524
  { className: 'dshph-head' },
378
- h('span', { className: 'dshph-dot' }),
525
+ h('span', {
526
+ className: 'dshph-dot' + (soon ? ' dshph-dot-soon' : ''),
527
+ }),
379
528
  h('span', { className: 'dshph-label' }, stateText),
380
529
  h(
381
530
  'span',
@@ -383,7 +532,7 @@ window.__ModuleLoader__.load({
383
532
  off ? t('price.discount') : t('price.multiplier'),
384
533
  ),
385
534
  h('span', { className: 'dshph-spacer' }),
386
- h('span', { className: 'dshph-next' }, remaining),
535
+ headText,
387
536
  ),
388
537
  track,
389
538
  );
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "dsh-peak-hours",
3
- "version": "1.10.1",
3
+ "version": "1.11.0",
4
4
  "type": "module",
5
- "description": "Sidebar badge that marks the current DeepSeek peak-pricing window, with a 24-hour schedule bar, a now-cursor and a countdown to the next switch.",
5
+ "description": "Sidebar badge that marks the current DeepSeek peak-pricing window: 24-hour schedule bar, now-cursor, countdown to the next switch, click to copy the state.",
6
6
  "license": "MIT",
7
7
  "author": "run666888 <zhirunli6@gmail.com>",
8
8
  "homepage": "https://github.com/run666888/dsh-peak-hours#readme",
@@ -13,11 +13,19 @@
13
13
  "bugs": {
14
14
  "url": "https://github.com/run666888/dsh-peak-hours/issues"
15
15
  },
16
+ "engines": {
17
+ "node": ">=18"
18
+ },
19
+ "scripts": {
20
+ "test": "node --test",
21
+ "verify": "npm pack --dry-run"
22
+ },
16
23
  "keywords": [
17
24
  "dsh",
18
25
  "dsh-plugin",
19
26
  "deepseek-harness",
20
27
  "peak-hours",
28
+ "off-peak",
21
29
  "pricing",
22
30
  "sidebar"
23
31
  ],
@@ -28,7 +36,8 @@
28
36
  ".": "./index.js",
29
37
  "./client": "./client.js",
30
38
  "./locale/*.json": "./locale/*.json",
31
- "./icon": "./icon.svg"
39
+ "./icon": "./icon.svg",
40
+ "./package.json": "./package.json"
32
41
  },
33
42
  "files": [
34
43
  "index.js",
@@ -40,6 +49,7 @@
40
49
  "screenshots",
41
50
  "screenshots.json",
42
51
  "README.md",
52
+ "CHANGELOG.md",
43
53
  "LICENSE"
44
54
  ],
45
55
  "dsh": {