dsh-niuma 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -38,7 +38,7 @@ module.exports = __toCommonJS(index_exports);
38
38
  var import_react = __toESM(require("react"), 1);
39
39
 
40
40
  // src/holidays.mjs
41
- var DEFAULT_HOLIDAYS = Object.freeze([
41
+ var OFFICIAL_HOLIDAYS_2026 = Object.freeze([
42
42
  // 元旦:1月1日(周四)至3日(周六)
43
43
  "2026-01-01",
44
44
  "2026-01-02",
@@ -80,7 +80,7 @@ var DEFAULT_HOLIDAYS = Object.freeze([
80
80
  "2026-10-06",
81
81
  "2026-10-07"
82
82
  ]);
83
- var DEFAULT_EXTRA_WORKDAYS = Object.freeze([
83
+ var OFFICIAL_EXTRA_WORKDAYS_2026 = Object.freeze([
84
84
  "2026-01-04",
85
85
  // 元旦调休:周日上班
86
86
  "2026-02-14",
@@ -94,11 +94,88 @@ var DEFAULT_EXTRA_WORKDAYS = Object.freeze([
94
94
  "2026-10-10"
95
95
  // 国庆调休:周六上班
96
96
  ]);
97
+ var HOLIDAY_BLOCKS = Object.freeze([
98
+ { id: "newYear", year: 2026, from: "2026-01-01", to: "2026-01-03", days: 3, estimated: false },
99
+ { id: "spring", year: 2026, from: "2026-02-15", to: "2026-02-23", days: 9, estimated: false },
100
+ { id: "qingming", year: 2026, from: "2026-04-04", to: "2026-04-06", days: 3, estimated: false },
101
+ { id: "labour", year: 2026, from: "2026-05-01", to: "2026-05-05", days: 5, estimated: false },
102
+ { id: "dragon", year: 2026, from: "2026-06-19", to: "2026-06-21", days: 3, estimated: false },
103
+ { id: "midAutumn", year: 2026, from: "2026-09-25", to: "2026-09-27", days: 3, estimated: false },
104
+ { id: "national", year: 2026, from: "2026-10-01", to: "2026-10-07", days: 7, estimated: false }
105
+ ]);
97
106
  var HOLIDAY_SOURCE = Object.freeze({
98
107
  year: 2026,
108
+ estimated: false,
99
109
  title: "\u56FD\u52A1\u9662\u529E\u516C\u5385\u5173\u4E8E2026\u5E74\u90E8\u5206\u8282\u5047\u65E5\u5B89\u6392\u7684\u901A\u77E5\uFF08\u56FD\u529E\u53D1\u660E\u7535\u30142025\u30157\u53F7\uFF09",
100
110
  url: "https://www.gov.cn/zhengce/content/202511/content_7047090.htm"
101
111
  });
112
+ var ESTIMATED_HOLIDAYS_2027 = Object.freeze([
113
+ "2027-01-01",
114
+ "2027-01-02",
115
+ "2027-01-03",
116
+ "2027-02-05",
117
+ "2027-02-06",
118
+ "2027-02-07",
119
+ "2027-02-08",
120
+ "2027-02-09",
121
+ "2027-02-10",
122
+ "2027-02-11",
123
+ "2027-02-12",
124
+ "2027-02-13",
125
+ "2027-04-03",
126
+ "2027-04-04",
127
+ "2027-04-05",
128
+ "2027-05-01",
129
+ "2027-05-02",
130
+ "2027-05-03",
131
+ "2027-05-04",
132
+ "2027-05-05",
133
+ "2027-06-09",
134
+ "2027-09-15",
135
+ "2027-10-01",
136
+ "2027-10-02",
137
+ "2027-10-03",
138
+ "2027-10-04",
139
+ "2027-10-05",
140
+ "2027-10-06",
141
+ "2027-10-07"
142
+ ]);
143
+ var ESTIMATED_EXTRA_WORKDAYS_2027 = Object.freeze([
144
+ "2027-01-31",
145
+ // 春节调休:周日上班(预估)
146
+ "2027-02-14",
147
+ // 春节调休:周日上班(预估)
148
+ "2027-05-08",
149
+ // 劳动节调休:周六上班(预估)
150
+ "2027-09-26",
151
+ // 国庆调休:周日上班(预估)
152
+ "2027-10-09"
153
+ // 国庆调休:周六上班(预估)
154
+ ]);
155
+ var HOLIDAY_BLOCKS_2027_ESTIMATED = Object.freeze([
156
+ { id: "newYear", year: 2027, from: "2027-01-01", to: "2027-01-03", days: 3, estimated: true },
157
+ { id: "spring", year: 2027, from: "2027-02-05", to: "2027-02-13", days: 9, estimated: true },
158
+ { id: "qingming", year: 2027, from: "2027-04-03", to: "2027-04-05", days: 3, estimated: true },
159
+ { id: "labour", year: 2027, from: "2027-05-01", to: "2027-05-05", days: 5, estimated: true },
160
+ { id: "dragon", year: 2027, from: "2027-06-09", to: "2027-06-09", days: 1, estimated: true },
161
+ { id: "midAutumn", year: 2027, from: "2027-09-15", to: "2027-09-15", days: 1, estimated: true },
162
+ { id: "national", year: 2027, from: "2027-10-01", to: "2027-10-07", days: 7, estimated: true }
163
+ ]);
164
+ var HOLIDAY_ESTIMATE_2027 = Object.freeze({
165
+ year: 2027,
166
+ estimated: true,
167
+ announced: false,
168
+ title: "2027 \u5E74\u653E\u5047\u5B89\u6392\uFF08\u9884\u4F30\uFF0C\u5B98\u65B9\u5C1A\u672A\u516C\u5E03\uFF09",
169
+ note: "\u5B98\u65B9\u901A\u77E5\u901A\u5E38\u5728\u524D\u4E00\u5E74 11 \u6708\u521D\u53D1\u5E03\uFF1B\u4EE5\u4E0B\u4E3A\u6309 2024 \u5E74\u4FEE\u8BA2\u7684\u300A\u5168\u56FD\u5E74\u8282\u53CA\u7EAA\u5FF5\u65E5\u653E\u5047\u529E\u6CD5\u300B\u4E0E\u5F80\u5E74\u60EF\u4F8B\u63A8\u7B97\uFF0C\u4EC5\u4F9B\u53C2\u8003\uFF0C\u8BF7\u4EE5\u56FD\u52A1\u9662\u901A\u77E5\u4E3A\u51C6\u3002",
170
+ basis: [
171
+ "\u6625\u8282\u81EA\u9664\u5915\u8D77\u653E\u5047\u8C03\u4F11\uFF0C\u4E00\u822C\u8FDE\u4F11 8\u20139 \u5929",
172
+ "\u56FD\u5E86 7 \u5929\u3001\u52B3\u52A8\u8282 5 \u5929",
173
+ "\u5143\u65E6 / \u6E05\u660E / \u7AEF\u5348 / \u4E2D\u79CB\u5404 1 \u5929\u6CD5\u5B9A\u5047\uFF0C\u9047\u5468\u672B\u987A\u8FDE"
174
+ ],
175
+ url: "https://www.gov.cn/zhengce/xxgk/"
176
+ });
177
+ var DEFAULT_HOLIDAYS = Object.freeze([...OFFICIAL_HOLIDAYS_2026, ...ESTIMATED_HOLIDAYS_2027]);
178
+ var DEFAULT_EXTRA_WORKDAYS = Object.freeze([...OFFICIAL_EXTRA_WORKDAYS_2026, ...ESTIMATED_EXTRA_WORKDAYS_2027]);
102
179
 
103
180
  // src/core.mjs
104
181
  var DEFAULTS = Object.freeze({
@@ -307,6 +384,13 @@ function earnedToday(config, now) {
307
384
  const monthEarned = earnedThisMonth(config, key, date, todayEarned, rates);
308
385
  const secondsToStart = workday && nowSec < startSec ? startSec - nowSec : 0;
309
386
  const secondsToEnd = workday && nowSec < endSec ? endSec - nowSec : 0;
387
+ const monthWorkdays = rates.monthlyWorkdays;
388
+ let monthWorkdaysDone = 0;
389
+ for (let day = 1; day < date.getDate(); day += 1) {
390
+ if (isWorkday(dateKeyOf(new Date(date.getFullYear(), date.getMonth(), day)), config)) monthWorkdaysDone += 1;
391
+ }
392
+ if (workday && status === "off") monthWorkdaysDone += 1;
393
+ const monthWorkdaysLeft = Math.max(0, monthWorkdays - monthWorkdaysDone);
310
394
  return {
311
395
  dateKey: key,
312
396
  isWorkday: workday,
@@ -317,6 +401,9 @@ function earnedToday(config, now) {
317
401
  todayTarget,
318
402
  todayProgress,
319
403
  monthEarned,
404
+ monthWorkdays,
405
+ monthWorkdaysDone,
406
+ monthWorkdaysLeft,
320
407
  secondsToStart,
321
408
  secondsToEnd,
322
409
  rates
@@ -359,6 +446,68 @@ function formatDuration(seconds) {
359
446
  if (minutes > 0) return `${minutes}\u5206 ${rest}\u79D2`;
360
447
  return `${rest}\u79D2`;
361
448
  }
449
+ function holidayDaysInMonth(year, monthIndex, config) {
450
+ const prefix = `${year}-${String(monthIndex + 1).padStart(2, "0")}-`;
451
+ return (config.holidays ?? DEFAULTS.holidays).filter((key) => key.startsWith(prefix)).length;
452
+ }
453
+ function monthSummary(config, year, monthIndex) {
454
+ const daysInMonth = new Date(year, monthIndex + 1, 0).getDate();
455
+ const workdays = workdaysInMonth(year, monthIndex, config);
456
+ const holidayDays = holidayDaysInMonth(year, monthIndex, config);
457
+ const monthlySalary = Number(config.monthlySalary) || 0;
458
+ const paidMinutes = paidMinutesPerDay(config);
459
+ const paidHours = paidMinutes / 60;
460
+ const dailyWage = workdays > 0 ? monthlySalary / workdays : 0;
461
+ const hourlyWage = paidHours > 0 ? dailyWage / paidHours : 0;
462
+ return {
463
+ year,
464
+ monthIndex,
465
+ month: monthIndex + 1,
466
+ daysInMonth,
467
+ workdays,
468
+ holidayDays,
469
+ restDays: daysInMonth - workdays,
470
+ monthlySalary,
471
+ paidMinutes,
472
+ paidHours,
473
+ dailyWage,
474
+ hourlyWage,
475
+ minuteWage: hourlyWage / 60,
476
+ secondWage: hourlyWage / 3600
477
+ };
478
+ }
479
+ function yearSummary(config, year) {
480
+ const months = Array.from({ length: 12 }, (_, monthIndex) => monthSummary(config, year, monthIndex));
481
+ const totalWorkdays = months.reduce((sum, item) => sum + item.workdays, 0);
482
+ const totalHolidayDays = months.reduce((sum, item) => sum + item.holidayDays, 0);
483
+ const monthlySalary = Number(config.monthlySalary) || 0;
484
+ const annualIncome = monthlySalary * 12;
485
+ const paidMinutes = paidMinutesPerDay(config);
486
+ const paidHours = paidMinutes / 60;
487
+ const averageDailyWage = totalWorkdays > 0 ? annualIncome / totalWorkdays : 0;
488
+ const averageHourlyWage = paidHours > 0 ? averageDailyWage / paidHours : 0;
489
+ const maxHolidayMonth = months.reduce((best, item) => {
490
+ if (item.holidayDays !== best.holidayDays) return item.holidayDays > best.holidayDays ? item : best;
491
+ if (item.workdays !== best.workdays) return item.workdays < best.workdays ? item : best;
492
+ return item.month < best.month ? item : best;
493
+ }, months[0]);
494
+ return {
495
+ year,
496
+ months,
497
+ totalWorkdays,
498
+ totalHolidayDays,
499
+ paidMinutes,
500
+ paidHours,
501
+ monthlySalary,
502
+ annualIncome,
503
+ averageMonthlyWage: monthlySalary,
504
+ averageDailyWage,
505
+ averageHourlyWage,
506
+ averageMinuteWage: averageHourlyWage / 60,
507
+ averageSecondWage: averageHourlyWage / 3600,
508
+ maxHolidayMonth
509
+ };
510
+ }
362
511
 
363
512
  // src/client/index.js
364
513
  var PKG_NAME = true ? "dsh-niuma" : "dsh-niuma";
@@ -388,6 +537,15 @@ var CARD = {
388
537
  boxSizing: "border-box"
389
538
  };
390
539
  var NUM = { fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" };
540
+ var GOLD = {
541
+ color: "#D9A441",
542
+ backgroundImage: "linear-gradient(180deg, #FFE9A8 0%, #F2C14E 40%, #D79A2B 72%, #B87A16 100%)",
543
+ WebkitBackgroundClip: "text",
544
+ backgroundClip: "text",
545
+ WebkitTextFillColor: "transparent",
546
+ textShadow: "0 1px 0 rgba(0,0,0,.05)"
547
+ };
548
+ var GOLD_TINT = "#E7B84D";
391
549
  var STATUS_META = {
392
550
  rest: { key: "statusRest", color: C.label3 },
393
551
  before: { key: "statusBefore", color: C.warn },
@@ -423,28 +581,75 @@ var zh = {
423
581
  "paidHoursPerDay": "\u65E5\u6709\u6548\u5DE5\u65F6",
424
582
  "todayTarget": "\u4ECA\u65E5\u76EE\u6807",
425
583
  "monthEarned": "\u672C\u6708\u5DF2\u8D5A",
426
- "holidaySource": "\u8282\u5047\u65E5\u6765\u6E90",
584
+ "monthWorkedDays": "\u672C\u6708\u5DF2\u5DE5\u4F5C",
585
+ "monthRemainingDays": "\u5269\u4F59\u5DE5\u4F5C\u65E5",
427
586
  "holidaySourceLink": "\u56FD\u52A1\u9662\u529E\u516C\u5385 2026 \u5E74\u8282\u5047\u65E5\u5B89\u6392",
428
587
  "configLoading": "\u6B63\u5728\u8BFB\u53D6\u8BBE\u7F6E\u2026",
429
588
  "configFailed": "\u8BBE\u7F6E\u8BFB\u53D6\u5931\u8D25\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u503C",
430
589
  "retry": "\u91CD\u8BD5",
431
590
  "settings": "\u8BBE\u7F6E",
432
591
  "settingsHint": "\u6539\u5B8C\u70B9\u300C\u4FDD\u5B58\u300D\u751F\u6548\uFF1B\u683C\u5F0F\u4E0D\u5BF9\u4F1A\u5728\u4E0B\u65B9\u7EA2\u5B57\u63D0\u793A\uFF0C\u4E0D\u5F71\u54CD\u8BA1\u65F6\u3002",
433
- "salaryField": "\u5B9E\u6536\u6708\u85AA\uFF08\u5143/\u6708\uFF09",
592
+ "salaryField": "\u5B9E\u6536\u6708\u85AA",
593
+ "groupSalary": "\u5DE5\u8D44",
594
+ "groupWorkTime": "\u5DE5\u4F5C\u65F6\u95F4",
595
+ "groupLunch": "\u5348\u4F11",
596
+ "salaryUnit": "\u5143 / \u6708",
434
597
  "workStartField": "\u4E0A\u73ED\u65F6\u95F4",
435
598
  "workEndField": "\u4E0B\u73ED\u65F6\u95F4",
436
599
  "lunchEnabledField": "\u542F\u7528\u5348\u4F11",
600
+ "lunchEnabledShort": "\u542F\u7528",
437
601
  "lunchStartField": "\u5348\u4F11\u5F00\u59CB",
438
602
  "lunchEndField": "\u5348\u4F11\u7ED3\u675F",
439
603
  "lunchPaidField": "\u5348\u4F11\u7B97\u5DE5\u65F6",
440
- "holidaysField": "\u8282\u5047\u65E5\uFF08\u4E00\u884C\u4E00\u4E2A YYYY-MM-DD\uFF09",
441
- "extraWorkdaysField": "\u8C03\u4F11\u4E0A\u73ED\u65E5\uFF08\u4E00\u884C\u4E00\u4E2A YYYY-MM-DD\uFF09",
604
+ "lunchPaidShort": "\u7B97\u5DE5\u65F6",
605
+ "holidaysCardTitle": "\u8282\u5047\u65E5\u5B89\u6392",
606
+ "holidays2026Label": "2026 \u5E74\uFF08\u5B98\u65B9\uFF09",
607
+ "holidays2027Label": "2027 \u5E74\uFF08\u9884\u4F30\uFF09",
608
+ "holidayEstimateTag": "\u9884\u4F30",
609
+ "holidayEstimateNote": "\u5B98\u65B9\u5C1A\u672A\u516C\u5E03\uFF0C\u4EE5\u4E0A\u4E3A\u6309\u89C4\u5219\u4E0E\u60EF\u4F8B\u63A8\u7B97\uFF0C\u4EC5\u4F9B\u53C2\u8003\uFF0C\u4EE5\u56FD\u52A1\u9662\u901A\u77E5\u4E3A\u51C6\u3002",
610
+ "holidayEstimateShort": "2027 \u4E3A\u9884\u4F30\u503C",
611
+ "settingsDataNote": "\u8282\u5047\u65E5\u4E0E\u8C03\u4F11\u6570\u636E\u89C1\u4E0A\u65B9\u300C\u8282\u5047\u65E5\u5B89\u6392\u300D\u5361\u7247\uFF082027 \u5E74\u4E3A\u9884\u4F30\u503C\uFF0C\u4E0D\u53EF\u5728\u672C\u9875\u7F16\u8F91\uFF09\u3002",
612
+ "holidaysCardSub": "\u6CD5\u5B9A\u5047\u65E5 {days} \u5929 \xB7 \u8C03\u4F11\u4E0A\u73ED {extra} \u5929",
613
+ "holidaysDaysShort": "{days} \u5929",
614
+ "holidaysExtraNote": "\u8C03\u4F11\u4E0A\u73ED\u65E5 {extra} \u5929\uFF08\u5468\u672B\u8865\u73ED\uFF0C\u6309\u5DE5\u4F5C\u65E5\u8BA1\uFF09",
615
+ "holidayNewYear": "\u5143\u65E6",
616
+ "holidaySpring": "\u6625\u8282",
617
+ "holidayQingming": "\u6E05\u660E",
618
+ "holidayLabour": "\u52B3\u52A8\u8282",
619
+ "holidayDragon": "\u7AEF\u5348",
620
+ "holidayMidAutumn": "\u4E2D\u79CB",
621
+ "holidayNational": "\u56FD\u5E86",
442
622
  "save": "\u4FDD\u5B58",
443
623
  "saving": "\u4FDD\u5B58\u4E2D\u2026",
444
624
  "saved": "\u5DF2\u4FDD\u5B58",
445
625
  "saveFailed": "\u4FDD\u5B58\u5931\u8D25\uFF1A",
446
626
  "dayUnit": "\u5929",
447
- "hourUnit": "\u5C0F\u65F6"
627
+ "hourUnit": "\u5C0F\u65F6",
628
+ "minuteUnit": "\u5206\u949F",
629
+ "viewLabel": "\u67E5\u770B",
630
+ "viewCurrent": "\u672C\u6708",
631
+ "viewYear": "\u5168\u5E74\u5E73\u5747",
632
+ "monthUnit": "\u6708",
633
+ "holidayShort": "\u5047",
634
+ "currentHint": "\u6309\u6BCF\u6708 {days} \u4E2A\u5DE5\u4F5C\u65E5\u3001\u6BCF\u5929 {hours} \u5C0F\u65F6\u8BA1",
635
+ "monthHint": "{year} \u5E74 {month} \u6708 \xB7 \u5DE5\u4F5C\u65E5 {workdays} \u5929 \xB7 \u6CD5\u5B9A\u5047\u65E5 {holidays} \u5929",
636
+ "yearHint": "{year} \u5168\u5E74 \xB7 {workdays} \u4E2A\u5DE5\u4F5C\u65E5 \xB7 \u6309\u5E74\u6536\u5165\u6298\u7B97",
637
+ "viewOnlyHint": "\u4EC5\u6362\u7B97\uFF0C\u4E0D\u5F71\u54CD\u4ECA\u65E5\u8BA1\u6570\u5668",
638
+ "yearWorkdays": "\u5168\u5E74\u5DE5\u4F5C\u65E5",
639
+ "yearIncome": "\u5168\u5E74\u6536\u5165",
640
+ "averageDaily": "\u5E73\u5747\u65E5\u85AA",
641
+ "averageHourly": "\u5E73\u5747\u65F6\u85AA",
642
+ "averageMinute": "\u5E73\u5747\u5206\u85AA",
643
+ "averageSecond": "\u5E73\u5747\u79D2\u85AA",
644
+ "monthWorkdaysCard": "\u5DE5\u4F5C\u65E5",
645
+ "maxHolidayBadge": "\u5168\u5E74\u5047\u65E5\u6700\u591A",
646
+ "maxHolidayNote": "\u5168\u5E74\u5047\u65E5\u6700\u591A\uFF1A{month} \u6708\uFF08\u6CD5\u5B9A\u5047\u65E5 {days} \u5929\uFF09",
647
+ "maxHolidayThis": "\u5168\u5E74\u5047\u65E5\u6700\u591A\u7684\u6708\u4EFD\uFF08\u6CD5\u5B9A\u5047\u65E5 {days} \u5929\uFF09",
648
+ "sectionRates": "\u5DE5\u8D44\u6362\u7B97",
649
+ "sectionRatesHint": "\u6309\u6BCF\u6708 {days} \u4E2A\u5DE5\u4F5C\u65E5\u3001\u6BCF\u5929 {hours} \u5C0F\u65F6\u8BA1",
650
+ "sectionDetail": "\u4ECA\u65E5\u53E3\u5F84\u660E\u7EC6",
651
+ "restHint": "\u4ECA\u5929\u4F11\u606F\uFF0C\u597D\u597D\u8EBA\u5E73",
652
+ "liveNow": "\u5B9E\u65F6"
448
653
  };
449
654
  var en = {
450
655
  "nav": "Niuma",
@@ -474,38 +679,82 @@ var en = {
474
679
  "paidHoursPerDay": "Paid hours / day",
475
680
  "todayTarget": "Today's target",
476
681
  "monthEarned": "Earned this month",
477
- "holidaySource": "Holiday data",
682
+ "monthWorkedDays": "Worked this month",
683
+ "monthRemainingDays": "Workdays left",
478
684
  "holidaySourceLink": "State Council 2026 holiday schedule",
479
685
  "configLoading": "Loading settings\u2026",
480
686
  "configFailed": "Failed to load settings, using defaults",
481
687
  "retry": "Retry",
482
688
  "settings": "Settings",
483
689
  "settingsHint": "Click Save to apply. Invalid input is shown in red below; the ticker keeps running.",
484
- "salaryField": "Net monthly salary (CNY)",
690
+ "salaryField": "Net monthly salary",
691
+ "groupSalary": "Salary",
692
+ "groupWorkTime": "Working hours",
693
+ "groupLunch": "Lunch break",
694
+ "salaryUnit": "CNY / month",
485
695
  "workStartField": "Work start",
486
696
  "workEndField": "Work end",
487
697
  "lunchEnabledField": "Enable lunch break",
698
+ "lunchEnabledShort": "On",
488
699
  "lunchStartField": "Lunch start",
489
700
  "lunchEndField": "Lunch end",
490
701
  "lunchPaidField": "Lunch is paid",
491
- "holidaysField": "Holidays (one YYYY-MM-DD per line)",
492
- "extraWorkdaysField": "Make-up workdays (one YYYY-MM-DD per line)",
702
+ "lunchPaidShort": "Paid",
703
+ "holidaysCardTitle": "Holiday calendar",
704
+ "holidays2026Label": "2026 (official)",
705
+ "holidays2027Label": "2027 (estimated)",
706
+ "holidayEstimateTag": "estimate",
707
+ "holidayEstimateNote": "Not announced officially yet \u2014 estimated from the rules and past practice, for reference only.",
708
+ "holidayEstimateShort": "2027 is estimated",
709
+ "settingsDataNote": "Holiday data lives in the calendar card above (2027 values are estimates and not editable here).",
710
+ "holidaysCardSub": "{days} statutory days \xB7 {extra} make-up workdays",
711
+ "holidaysDaysShort": "{days} d",
712
+ "holidaysExtraNote": "{extra} make-up workdays (weekend shifts, counted as workdays)",
713
+ "holidayNewYear": "New Year's Day",
714
+ "holidaySpring": "Spring Festival",
715
+ "holidayQingming": "Qingming",
716
+ "holidayLabour": "Labour Day",
717
+ "holidayDragon": "Dragon Boat",
718
+ "holidayMidAutumn": "Mid-Autumn",
719
+ "holidayNational": "National Day",
493
720
  "save": "Save",
494
721
  "saving": "Saving\u2026",
495
722
  "saved": "Saved",
496
723
  "saveFailed": "Save failed: ",
497
724
  "dayUnit": "d",
498
- "hourUnit": "h"
725
+ "hourUnit": "h",
726
+ "minuteUnit": "min",
727
+ "viewLabel": "View",
728
+ "viewCurrent": "This month",
729
+ "viewYear": "Year average",
730
+ "monthUnit": "M",
731
+ "holidayShort": "hol.",
732
+ "currentHint": "{days} workdays / month \xB7 {hours} h / day",
733
+ "monthHint": "{year}-{month} \xB7 {workdays} workdays \xB7 {holidays} statutory holidays",
734
+ "yearHint": "{year} \xB7 {workdays} workdays \xB7 averaged over the year",
735
+ "viewOnlyHint": "Conversion only \u2014 today's ticker is unaffected",
736
+ "yearWorkdays": "Workdays / year",
737
+ "yearIncome": "Annual income",
738
+ "averageDaily": "Avg per day",
739
+ "averageHourly": "Avg per hour",
740
+ "averageMinute": "Avg per minute",
741
+ "averageSecond": "Avg per second",
742
+ "monthWorkdaysCard": "Workdays",
743
+ "maxHolidayBadge": "Most holidays",
744
+ "maxHolidayNote": "Most holidays: month {month} ({days} statutory days)",
745
+ "maxHolidayThis": "The month with the most holidays ({days} statutory days)",
746
+ "sectionRates": "Pay rates",
747
+ "sectionRatesHint": "{days} workdays / month \xB7 {hours} h / day",
748
+ "sectionDetail": "Today's basis",
749
+ "restHint": "Day off \u2014 enjoy it",
750
+ "liveNow": "LIVE"
499
751
  };
500
752
  var fallbackTranslate = (key) => zh[key] != null ? zh[key] : key;
501
- var money = (value, digits = 2) => `\xA5${formatMoney(value, digits)}`;
753
+ var money = (value, digits = 2) => `${formatMoney(value, digits)} \u5143`;
502
754
  function trimNumber(value, digits = 2) {
503
755
  const fixed = Number(value || 0).toFixed(digits);
504
756
  return fixed.replace(/\.?0+$/, "") || "0";
505
757
  }
506
- function splitDateLines(text) {
507
- return String(text == null ? "" : text).split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
508
- }
509
758
  function formFromConfig(config) {
510
759
  return {
511
760
  monthlySalary: String(config.monthlySalary == null ? "" : config.monthlySalary),
@@ -514,9 +763,7 @@ function formFromConfig(config) {
514
763
  lunchEnabled: config.lunchEnabled !== false,
515
764
  lunchStart: String(config.lunchStart == null ? "" : config.lunchStart),
516
765
  lunchEnd: String(config.lunchEnd == null ? "" : config.lunchEnd),
517
- lunchPaid: config.lunchPaid === true,
518
- holidaysText: (config.holidays || []).join("\n"),
519
- extraWorkdaysText: (config.extraWorkdays || []).join("\n")
766
+ lunchPaid: config.lunchPaid === true
520
767
  };
521
768
  }
522
769
  function patchFromForm(form) {
@@ -528,9 +775,7 @@ function patchFromForm(form) {
528
775
  lunchEnabled: Boolean(form.lunchEnabled),
529
776
  lunchStart: String(form.lunchStart || "").trim(),
530
777
  lunchEnd: String(form.lunchEnd || "").trim(),
531
- lunchPaid: Boolean(form.lunchPaid),
532
- holidays: splitDateLines(form.holidaysText),
533
- extraWorkdays: splitDateLines(form.extraWorkdaysText)
778
+ lunchPaid: Boolean(form.lunchPaid)
534
779
  };
535
780
  }
536
781
  function useRemoteConfig() {
@@ -600,49 +845,110 @@ function NiumaIcon(props) {
600
845
  import_react.default.createElement("path", { key: "c", d: "M4 8h.01M12 8h.01" })
601
846
  ]);
602
847
  }
848
+ var PANEL_CSS = `
849
+ @keyframes niumaPulse { 0%,100% { opacity: .95; transform: scale(1); } 50% { opacity: .3; transform: scale(.68); } }
850
+ @media (prefers-reduced-motion: reduce) { .niuma-live-dot { animation: none !important; } }
851
+ `;
852
+ function StyleTag() {
853
+ return import_react.default.createElement("style", null, PANEL_CSS);
854
+ }
603
855
  function StatusBadge(props) {
604
856
  const t = props.t;
605
857
  const meta = STATUS_META[props.status] || STATUS_META.rest;
858
+ const live = props.status === "working";
606
859
  return import_react.default.createElement("span", {
607
860
  style: {
608
861
  display: "inline-flex",
609
862
  alignItems: "center",
610
863
  gap: 6,
611
- padding: "3px 10px",
864
+ padding: "4px 11px 4px 9px",
612
865
  borderRadius: 999,
613
866
  fontSize: 12,
614
867
  fontWeight: 600,
615
868
  lineHeight: 1.5,
616
869
  color: meta.color,
617
- border: `1px solid ${meta.color}`,
618
- background: C.layer1,
870
+ border: `1px solid color-mix(in srgb, ${meta.color} 42%, transparent)`,
871
+ background: `color-mix(in srgb, ${meta.color} 12%, transparent)`,
619
872
  whiteSpace: "nowrap"
620
873
  }
621
874
  }, [
622
875
  import_react.default.createElement("i", {
623
876
  key: "dot",
877
+ className: live ? "niuma-live-dot" : void 0,
624
878
  style: { width: 6, height: 6, borderRadius: "50%", background: meta.color, display: "inline-block" }
625
879
  }),
626
880
  t(meta.key)
627
881
  ]);
628
882
  }
883
+ function SectionTitle(props) {
884
+ return import_react.default.createElement("div", {
885
+ style: {
886
+ display: "flex",
887
+ alignItems: "baseline",
888
+ justifyContent: "space-between",
889
+ gap: 8,
890
+ flexWrap: "wrap",
891
+ margin: "2px 1px -1px"
892
+ }
893
+ }, [
894
+ import_react.default.createElement("span", {
895
+ key: "t",
896
+ style: { fontSize: 12.5, fontWeight: 700, color: C.label2, letterSpacing: ".2px" }
897
+ }, props.title),
898
+ props.hint ? import_react.default.createElement("span", { key: "h", style: { fontSize: 10.5, color: C.label3, ...NUM } }, props.hint) : null
899
+ ]);
900
+ }
629
901
  function HeroCard(props) {
630
902
  const t = props.t;
631
903
  const snap = props.snap;
632
- return import_react.default.createElement("div", { style: { ...CARD, padding: "13px 15px 12px" } }, [
904
+ const rates = snap.rates;
905
+ const progress = Math.max(0, Math.min(1, Number(snap.todayProgress) || 0));
906
+ const percentText = `${(progress * 100).toFixed(1)}%`;
907
+ const live = snap.status === "working";
908
+ const rest = snap.status === "rest";
909
+ const heroBackground = "linear-gradient(135deg, color-mix(in srgb, " + C.brand + " 17%, " + C.layer1 + ") 0%, " + C.layer1 + " 58%, " + C.layer1 + " 100%)";
910
+ return import_react.default.createElement("div", {
911
+ style: {
912
+ position: "relative",
913
+ overflow: "hidden",
914
+ borderRadius: 18,
915
+ padding: "16px 18px 14px",
916
+ border: `1px solid color-mix(in srgb, ${GOLD_TINT} 34%, ${C.border1})`,
917
+ background: heroBackground,
918
+ boxShadow: "0 12px 30px rgba(0,0,0,.10)",
919
+ boxSizing: "border-box",
920
+ minWidth: 0
921
+ }
922
+ }, [
633
923
  import_react.default.createElement("div", {
634
- key: "head",
924
+ key: "glow",
925
+ "aria-hidden": "true",
635
926
  style: {
636
- display: "flex",
637
- alignItems: "center",
638
- justifyContent: "space-between",
639
- gap: 8,
640
- flexWrap: "wrap",
641
- fontSize: 12,
642
- color: C.label2
927
+ position: "absolute",
928
+ right: -52,
929
+ top: -72,
930
+ width: 188,
931
+ height: 188,
932
+ borderRadius: "50%",
933
+ background: `radial-gradient(circle, color-mix(in srgb, ${GOLD_TINT} 30%, transparent) 0%, transparent 68%)`,
934
+ pointerEvents: "none"
643
935
  }
936
+ }),
937
+ import_react.default.createElement("div", {
938
+ key: "head",
939
+ style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8, flexWrap: "wrap" }
644
940
  }, [
645
- import_react.default.createElement("span", { key: "a" }, t("earnedToday")),
941
+ import_react.default.createElement("span", {
942
+ key: "a",
943
+ style: { display: "inline-flex", alignItems: "center", gap: 6, fontSize: 12.5, fontWeight: 600, color: C.label2 }
944
+ }, [
945
+ live ? import_react.default.createElement("i", {
946
+ key: "dot",
947
+ className: "niuma-live-dot",
948
+ style: { width: 7, height: 7, borderRadius: "50%", background: C.ok, display: "inline-block" }
949
+ }) : null,
950
+ t("earnedToday")
951
+ ]),
646
952
  import_react.default.createElement(
647
953
  "span",
648
954
  { key: "b", style: { fontSize: 11, color: C.label3, ...NUM } },
@@ -651,65 +957,61 @@ function HeroCard(props) {
651
957
  ]),
652
958
  import_react.default.createElement("div", {
653
959
  key: "amt",
654
- style: { display: "flex", alignItems: "baseline", gap: 6, marginTop: 1, flexWrap: "wrap" }
960
+ style: { display: "flex", alignItems: "baseline", gap: 7, marginTop: 4, flexWrap: "wrap" }
655
961
  }, [
656
- import_react.default.createElement("span", {
657
- key: "cur",
658
- style: { fontSize: 22, fontWeight: 700, color: C.label2, ...NUM }
659
- }, "\xA5"),
660
962
  import_react.default.createElement("span", {
661
963
  key: "val",
662
964
  style: {
663
- fontSize: 40,
664
- lineHeight: 1.15,
665
- fontWeight: 760,
666
- letterSpacing: "-.5px",
667
- color: C.ok,
668
- ...NUM
965
+ fontSize: 46,
966
+ lineHeight: 1.12,
967
+ fontWeight: 800,
968
+ letterSpacing: "-1.2px",
969
+ ...GOLD,
970
+ ...NUM,
971
+ ...rest ? { opacity: 0.62 } : {}
669
972
  }
670
- }, formatMoney(snap.todayEarned, 4))
973
+ }, formatMoney(snap.todayEarned, 4)),
974
+ import_react.default.createElement("span", {
975
+ key: "unit",
976
+ style: { fontSize: 18, fontWeight: 700, color: C.label2, ...NUM }
977
+ }, "\u5143")
671
978
  ]),
672
979
  import_react.default.createElement("div", {
673
- key: "sub",
674
- style: { fontSize: 11, color: C.label3, marginTop: 2, ...NUM }
675
- }, `${t("perSecond")} ${money(snap.rates.secondWage, 4)}`)
676
- ]);
677
- }
678
- function ProgressCard(props) {
679
- const t = props.t;
680
- const snap = props.snap;
681
- const progress = Math.max(0, Math.min(1, Number(snap.todayProgress) || 0));
682
- const percentText = `${(progress * 100).toFixed(1)}%`;
683
- const countdownChip = (key, label, seconds) => import_react.default.createElement("div", {
684
- key,
685
- style: { ...CARD, flex: "1 1 130px", minWidth: 118, padding: "8px 10px" }
686
- }, [
687
- import_react.default.createElement("div", { key: "l", style: { fontSize: 11, color: C.label3 } }, label),
688
- import_react.default.createElement("div", {
689
- key: "v",
690
- style: { fontSize: 14, fontWeight: 650, color: C.label, marginTop: 2, ...NUM }
691
- }, Number(seconds) > 0 ? formatDuration(seconds) : "\u2014")
692
- ]);
693
- return import_react.default.createElement("div", { style: CARD }, [
694
- import_react.default.createElement("div", {
695
- key: "top",
696
- style: { display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 8, flexWrap: "wrap" }
980
+ key: "chips",
981
+ style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8, marginTop: 5, flexWrap: "wrap" }
697
982
  }, [
698
- import_react.default.createElement("span", { key: "l", style: { fontSize: 12, color: C.label2 } }, t("todayProgress")),
699
983
  import_react.default.createElement("span", {
700
- key: "p",
701
- style: { fontSize: 13, fontWeight: 650, color: C.brand, ...NUM }
702
- }, percentText)
984
+ key: "per",
985
+ style: {
986
+ display: "inline-flex",
987
+ alignItems: "center",
988
+ gap: 4,
989
+ fontSize: 11.5,
990
+ fontWeight: 600,
991
+ color: C.label2,
992
+ background: `color-mix(in srgb, ${C.brand} 11%, transparent)`,
993
+ border: `1px solid color-mix(in srgb, ${C.brand} 22%, transparent)`,
994
+ borderRadius: 999,
995
+ padding: "2px 9px",
996
+ ...NUM
997
+ }
998
+ }, `${t("perSecond")} +${money(rates.secondWage, 4)}`),
999
+ import_react.default.createElement(
1000
+ "span",
1001
+ { key: "target", style: { fontSize: 11, color: C.label3, ...NUM } },
1002
+ rest ? t("restHint") : `${t("todayTarget")} ${money(snap.todayTarget, 2)}`
1003
+ )
1004
+ ]),
1005
+ import_react.default.createElement("div", {
1006
+ key: "progTop",
1007
+ style: { display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 8, marginTop: 13, flexWrap: "wrap" }
1008
+ }, [
1009
+ import_react.default.createElement("span", { key: "l", style: { fontSize: 11.5, color: C.label3 } }, t("todayProgress")),
1010
+ import_react.default.createElement("span", { key: "p", style: { fontSize: 12.5, fontWeight: 700, color: C.brand, ...NUM } }, percentText)
703
1011
  ]),
704
1012
  import_react.default.createElement("div", {
705
1013
  key: "bar",
706
- style: {
707
- marginTop: 6,
708
- height: 8,
709
- borderRadius: 999,
710
- background: C.layer2,
711
- overflow: "hidden"
712
- }
1014
+ style: { marginTop: 5, height: 9, borderRadius: 999, background: `color-mix(in srgb, ${C.label} 9%, transparent)`, overflow: "hidden" }
713
1015
  }, [
714
1016
  import_react.default.createElement("div", {
715
1017
  key: "fill",
@@ -717,31 +1019,60 @@ function ProgressCard(props) {
717
1019
  width: `${(progress * 100).toFixed(2)}%`,
718
1020
  height: "100%",
719
1021
  borderRadius: 999,
720
- background: C.brand,
721
- transition: "width .4s linear"
1022
+ background: `linear-gradient(90deg, ${C.brand} 0%, color-mix(in srgb, ${C.brand} 55%, ${C.ok}) 100%)`,
1023
+ transition: "width .35s linear"
722
1024
  }
723
1025
  })
724
1026
  ]),
725
1027
  import_react.default.createElement("div", {
726
- key: "nums",
727
- style: { display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 8, marginTop: 5, flexWrap: "wrap", fontSize: 11, color: C.label3 }
1028
+ key: "progNum",
1029
+ style: { display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 8, marginTop: 5, flexWrap: "wrap", fontSize: 10.5, color: C.label3 }
728
1030
  }, [
729
1031
  import_react.default.createElement("span", { key: "a" }, t("progressOfTarget")),
730
1032
  import_react.default.createElement(
731
1033
  "span",
732
- { key: "b", style: { ...NUM } },
1034
+ { key: "b", style: { color: C.label2, fontWeight: 600, ...NUM } },
733
1035
  `${money(snap.todayEarned, 2)} / ${money(snap.todayTarget, 2)}`
734
1036
  )
735
- ]),
736
- import_react.default.createElement("div", {
737
- key: "cd",
738
- style: { display: "flex", flexWrap: "wrap", gap: 8, marginTop: 8 }
739
- }, [
740
- countdownChip("start", t("toStart"), snap.secondsToStart),
741
- countdownChip("end", t("toEnd"), snap.secondsToEnd)
742
1037
  ])
743
1038
  ]);
744
1039
  }
1040
+ function CountdownRow(props) {
1041
+ const t = props.t;
1042
+ const snap = props.snap;
1043
+ const chip = (key, label, value, accent) => import_react.default.createElement("div", {
1044
+ key,
1045
+ style: {
1046
+ flex: "1 1 120px",
1047
+ minWidth: 116,
1048
+ display: "flex",
1049
+ alignItems: "baseline",
1050
+ justifyContent: "space-between",
1051
+ gap: 8,
1052
+ padding: "9px 12px",
1053
+ borderRadius: 12,
1054
+ background: accent ? `color-mix(in srgb, ${GOLD_TINT} 9%, ${C.layer1})` : C.layer1,
1055
+ border: `1px solid ${accent ? `color-mix(in srgb, ${GOLD_TINT} 38%, ${C.border1})` : C.border1}`,
1056
+ boxSizing: "border-box"
1057
+ }
1058
+ }, [
1059
+ import_react.default.createElement("span", {
1060
+ key: "l",
1061
+ style: { fontSize: 11, color: accent ? C.label2 : C.label3, whiteSpace: "nowrap" }
1062
+ }, label),
1063
+ import_react.default.createElement("span", {
1064
+ key: "v",
1065
+ style: { fontSize: 13.5, fontWeight: 700, color: C.label, ...NUM }
1066
+ }, value)
1067
+ ]);
1068
+ return import_react.default.createElement("div", {
1069
+ style: { display: "flex", flexWrap: "wrap", gap: 9 }
1070
+ }, [
1071
+ chip("start", t("toStart"), Number(snap.secondsToStart) > 0 ? formatDuration(snap.secondsToStart) : "\u2014"),
1072
+ chip("end", t("toEnd"), Number(snap.secondsToEnd) > 0 ? formatDuration(snap.secondsToEnd) : "\u2014"),
1073
+ chip("month", t("monthEarned"), money(snap.monthEarned, 2), true)
1074
+ ]);
1075
+ }
745
1076
  var RATE_CARDS = [
746
1077
  { key: "secondWage", label: "secondWage", digits: 4 },
747
1078
  { key: "minuteWage", label: "minuteWage", digits: 4 },
@@ -751,65 +1082,247 @@ var RATE_CARDS = [
751
1082
  { key: "monthlyWage", label: "monthlyWage", digits: 2 }
752
1083
  ];
753
1084
  function RatesGrid(props) {
754
- const t = props.t;
755
- const rates = props.rates;
1085
+ const cards = Array.isArray(props.cards) ? props.cards : [];
756
1086
  return import_react.default.createElement("div", {
757
1087
  style: { display: "grid", gridTemplateColumns: "repeat(3, minmax(0, 1fr))", gap: 8 }
758
- }, RATE_CARDS.map((spec, index) => import_react.default.createElement("div", {
759
- key: spec.key,
760
- "data-stat": `stat-${index}`,
761
- style: { ...CARD, padding: "9px 10px" }
1088
+ }, cards.map((card, index) => {
1089
+ const accent = card.accent === true;
1090
+ return import_react.default.createElement("div", {
1091
+ key: card.key,
1092
+ "data-stat": `stat-${index}`,
1093
+ style: {
1094
+ ...CARD,
1095
+ padding: "10px 11px",
1096
+ borderRadius: 12,
1097
+ ...accent ? {
1098
+ border: `1px solid color-mix(in srgb, ${GOLD_TINT} 46%, ${C.border1})`,
1099
+ background: `color-mix(in srgb, ${GOLD_TINT} 9%, ${C.layer1})`
1100
+ } : {}
1101
+ }
1102
+ }, [
1103
+ import_react.default.createElement("div", {
1104
+ key: "l",
1105
+ style: { fontSize: 11, color: accent ? C.label2 : C.label3, fontWeight: accent ? 600 : 400 }
1106
+ }, card.label),
1107
+ import_react.default.createElement("div", {
1108
+ key: "v",
1109
+ style: {
1110
+ fontSize: 15.5,
1111
+ fontWeight: 700,
1112
+ letterSpacing: "-.2px",
1113
+ color: accent ? C.label : C.label,
1114
+ marginTop: 2,
1115
+ ...NUM
1116
+ }
1117
+ }, card.value)
1118
+ ]);
1119
+ }));
1120
+ }
1121
+ function PeriodSelect(props) {
1122
+ const t = props.t;
1123
+ const year = props.year;
1124
+ const months = props.months;
1125
+ const style = {
1126
+ appearance: "none",
1127
+ WebkitAppearance: "none",
1128
+ border: `1px solid ${C.border}`,
1129
+ background: C.layer2,
1130
+ color: C.label,
1131
+ borderRadius: 8,
1132
+ padding: "4px 9px",
1133
+ fontSize: 12,
1134
+ fontWeight: 600,
1135
+ cursor: "pointer",
1136
+ outline: "none",
1137
+ maxWidth: 220
1138
+ };
1139
+ return import_react.default.createElement("select", {
1140
+ value: props.value,
1141
+ "aria-label": t("viewLabel"),
1142
+ onChange: (event) => props.onChange(event.target.value),
1143
+ style
762
1144
  }, [
763
- import_react.default.createElement("div", { key: "l", style: { fontSize: 11, color: C.label3 } }, t(spec.label)),
764
- import_react.default.createElement("div", {
765
- key: "v",
766
- style: { fontSize: 15, fontWeight: 650, color: C.label, marginTop: 2, ...NUM }
767
- }, money(rates[spec.key], spec.digits))
768
- ])));
1145
+ import_react.default.createElement("option", { key: "current", value: "current" }, t("viewCurrent")),
1146
+ ...months.map((month) => import_react.default.createElement("option", {
1147
+ key: `m${month.month}`,
1148
+ value: `m${month.month}`
1149
+ }, `${month.month}${t("monthUnit")} \xB7 ${month.workdays}${t("dayUnit")} \xB7 ${t("holidayShort")}${month.holidayDays}`)),
1150
+ import_react.default.createElement("option", { key: "year", value: "year" }, `${year} ${t("viewYear")}`)
1151
+ ]);
769
1152
  }
770
1153
  function DetailsFooter(props) {
771
1154
  const t = props.t;
772
1155
  const snap = props.snap;
773
1156
  const rates = snap.rates;
774
- const source = props.holidaySource || HOLIDAY_SOURCE;
775
1157
  const items = [
776
1158
  { key: "salary", label: t("monthlySalaryLabel"), value: money(rates.monthlySalary, 2) },
777
1159
  { key: "workdays", label: t("workdaysThisMonth"), value: `${rates.monthlyWorkdays}${t("dayUnit")}` },
778
1160
  { key: "hours", label: t("paidHoursPerDay"), value: `${trimNumber(rates.paidHours, 2)}${t("hourUnit")}` },
779
1161
  { key: "target", label: t("todayTarget"), value: money(snap.todayTarget, 2) },
780
- { key: "month", label: t("monthEarned"), value: money(snap.monthEarned, 2) }
1162
+ { key: "month", label: t("monthEarned"), value: money(snap.monthEarned, 2) },
1163
+ { key: "worked", label: t("monthWorkedDays"), value: `${snap.monthWorkdaysDone}${t("dayUnit")}` },
1164
+ { key: "left", label: t("monthRemainingDays"), value: `${snap.monthWorkdaysLeft}${t("dayUnit")}` }
781
1165
  ];
782
1166
  return import_react.default.createElement("div", {
783
- style: { ...CARD, padding: "9px 12px", fontSize: 11, color: C.label3, lineHeight: 1.7 }
1167
+ style: {
1168
+ background: C.layer1,
1169
+ border: `1px solid ${C.border1}`,
1170
+ borderRadius: 12,
1171
+ padding: "10px 12px",
1172
+ fontSize: 11,
1173
+ color: C.label3,
1174
+ lineHeight: 1.6
1175
+ }
784
1176
  }, [
785
1177
  import_react.default.createElement("div", {
786
1178
  key: "items",
787
- style: { display: "flex", flexWrap: "wrap", gap: "2px 14px" }
788
- }, items.map((item) => import_react.default.createElement("span", {
1179
+ style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(118px, 1fr))", gap: "7px 12px" }
1180
+ }, items.map((item) => import_react.default.createElement("div", {
789
1181
  key: item.key,
790
- style: { display: "inline-flex", alignItems: "baseline", gap: 4, whiteSpace: "nowrap" }
1182
+ style: { minWidth: 0 }
791
1183
  }, [
792
- import_react.default.createElement("span", { key: "l" }, `${item.label}`),
793
- import_react.default.createElement("span", {
1184
+ import_react.default.createElement("div", { key: "l", style: { fontSize: 10.5, color: C.label3 } }, item.label),
1185
+ import_react.default.createElement("div", {
794
1186
  key: "v",
795
- style: { color: C.label2, fontWeight: 600, ...NUM }
1187
+ style: { fontSize: 13, fontWeight: 650, color: C.label2, ...NUM }
796
1188
  }, item.value)
1189
+ ])))
1190
+ ]);
1191
+ }
1192
+ var HOLIDAY_LABEL_KEYS = {
1193
+ newYear: "holidayNewYear",
1194
+ spring: "holidaySpring",
1195
+ qingming: "holidayQingming",
1196
+ labour: "holidayLabour",
1197
+ dragon: "holidayDragon",
1198
+ midAutumn: "holidayMidAutumn",
1199
+ national: "holidayNational"
1200
+ };
1201
+ function HolidaysCard(props) {
1202
+ const t = props.t;
1203
+ const source = props.holidaySource || HOLIDAY_SOURCE;
1204
+ const extra = Array.isArray(props.extraWorkdays) ? props.extraWorkdays : [];
1205
+ const countYear = (year) => extra.filter((key) => String(key).startsWith(`${year}-`)).length;
1206
+ const shortDate = (key) => key.slice(5).replace("-", "/");
1207
+ const sections = [
1208
+ {
1209
+ key: "official",
1210
+ label: t("holidays2026Label"),
1211
+ blocks: HOLIDAY_BLOCKS,
1212
+ estimated: false,
1213
+ extraCount: countYear(2026),
1214
+ tag: null
1215
+ },
1216
+ {
1217
+ key: "estimate",
1218
+ label: t("holidays2027Label"),
1219
+ blocks: HOLIDAY_BLOCKS_2027_ESTIMATED,
1220
+ estimated: true,
1221
+ extraCount: countYear(2027),
1222
+ tag: t("holidayEstimateTag")
1223
+ }
1224
+ ];
1225
+ const sumDays = (blocks) => blocks.reduce((sum, block) => sum + block.days, 0);
1226
+ const renderSection = (section) => import_react.default.createElement("div", { key: section.key, style: { marginTop: section.key === "official" ? 8 : 12 } }, [
1227
+ import_react.default.createElement("div", {
1228
+ key: "head",
1229
+ style: { display: "flex", alignItems: "baseline", flexWrap: "wrap", gap: 8 }
1230
+ }, [
1231
+ import_react.default.createElement("span", { key: "l", style: { fontSize: 11.5, fontWeight: 700, color: C.label2 } }, section.label),
1232
+ section.tag ? import_react.default.createElement("span", {
1233
+ key: "tag",
1234
+ style: {
1235
+ fontSize: 9.5,
1236
+ fontWeight: 700,
1237
+ color: C.label2,
1238
+ border: `1px dashed color-mix(in srgb, ${GOLD_TINT} 60%, transparent)`,
1239
+ borderRadius: 999,
1240
+ padding: "0 6px",
1241
+ lineHeight: 1.6
1242
+ }
1243
+ }, section.tag) : null,
1244
+ import_react.default.createElement(
1245
+ "span",
1246
+ { key: "s", style: { marginLeft: "auto", fontSize: 10, color: C.label3, ...NUM } },
1247
+ t("holidaysCardSub").replace("{days}", String(sumDays(section.blocks))).replace("{extra}", String(section.extraCount))
1248
+ )
1249
+ ]),
1250
+ import_react.default.createElement("div", {
1251
+ key: "grid",
1252
+ style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(118px, 1fr))", gap: 7, marginTop: 6 }
1253
+ }, section.blocks.map((block) => import_react.default.createElement("div", {
1254
+ key: block.id,
1255
+ style: {
1256
+ border: section.estimated ? `1px dashed color-mix(in srgb, ${GOLD_TINT} 40%, ${C.border1})` : `1px solid ${C.border1}`,
1257
+ borderRadius: 9,
1258
+ padding: "6px 8px",
1259
+ background: `color-mix(in srgb, ${GOLD_TINT} ${section.estimated ? 3 : 6}%, transparent)`
1260
+ }
1261
+ }, [
1262
+ import_react.default.createElement("div", {
1263
+ key: "row",
1264
+ style: { display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 6 }
1265
+ }, [
1266
+ import_react.default.createElement(
1267
+ "span",
1268
+ { key: "n", style: { fontSize: 11.5, fontWeight: 650, color: C.label } },
1269
+ t(HOLIDAY_LABEL_KEYS[block.id])
1270
+ ),
1271
+ import_react.default.createElement("span", {
1272
+ key: "d",
1273
+ style: {
1274
+ fontSize: 10,
1275
+ fontWeight: 700,
1276
+ color: C.label2,
1277
+ background: `color-mix(in srgb, ${GOLD_TINT} 16%, transparent)`,
1278
+ borderRadius: 999,
1279
+ padding: "0 6px",
1280
+ ...NUM
1281
+ }
1282
+ }, t("holidaysDaysShort").replace("{days}", String(block.days)))
1283
+ ]),
1284
+ import_react.default.createElement(
1285
+ "div",
1286
+ { key: "r", style: { fontSize: 10, color: C.label3, marginTop: 2, ...NUM } },
1287
+ `${shortDate(block.from)} \u2013 ${shortDate(block.to)}`
1288
+ )
797
1289
  ]))),
1290
+ section.estimated ? import_react.default.createElement("div", {
1291
+ key: "est-note",
1292
+ style: { marginTop: 6, fontSize: 10.5, color: C.label3, lineHeight: 1.5 }
1293
+ }, `\u26A0 ${t("holidayEstimateNote")}`) : null
1294
+ ]);
1295
+ return import_react.default.createElement("div", {
1296
+ style: {
1297
+ background: C.layer1,
1298
+ border: `1px solid ${C.border1}`,
1299
+ borderRadius: 12,
1300
+ padding: "10px 12px"
1301
+ }
1302
+ }, [
798
1303
  import_react.default.createElement("div", {
799
- key: "src",
800
- style: { display: "flex", flexWrap: "wrap", alignItems: "baseline", gap: 4, marginTop: 2 }
1304
+ key: "title",
1305
+ style: { display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 8, flexWrap: "wrap" }
801
1306
  }, [
802
- import_react.default.createElement("span", { key: "l" }, `${t("holidaySource")}\uFF1A`),
1307
+ import_react.default.createElement(
1308
+ "span",
1309
+ { key: "t", style: { fontSize: 12.5, fontWeight: 700, color: C.label2, letterSpacing: ".2px" } },
1310
+ t("holidaysCardTitle")
1311
+ ),
803
1312
  import_react.default.createElement("a", {
804
1313
  key: "a",
805
1314
  href: source.url,
806
1315
  target: "_blank",
807
1316
  rel: "noreferrer",
808
1317
  title: source.title,
809
- style: { color: C.brand, textDecoration: "underline" }
810
- }, `${source.year} \xB7 ${t("holidaySourceLink")} \u2197`),
811
- import_react.default.createElement("span", { key: "n", style: { color: C.label3 } }, `\uFF08${source.title}\uFF09`)
812
- ])
1318
+ style: { fontSize: 10.5, color: C.brand, textDecoration: "none", fontWeight: 600 }
1319
+ }, `${source.year} ${t("holidaySourceLink")} \u2197`)
1320
+ ]),
1321
+ sections.map(renderSection),
1322
+ import_react.default.createElement("div", {
1323
+ key: "foot",
1324
+ style: { marginTop: 8, fontSize: 10.5, color: C.label3 }
1325
+ }, t("holidaysExtraNote").replace("{extra}", String(extra.length)))
813
1326
  ]);
814
1327
  }
815
1328
  function SettingsSection(props) {
@@ -819,104 +1332,147 @@ function SettingsSection(props) {
819
1332
  const setCheck = props.setCheck;
820
1333
  const save = props.save;
821
1334
  const onSave = props.onSave;
1335
+ const rowStyle = {
1336
+ display: "flex",
1337
+ alignItems: "center",
1338
+ gap: 10,
1339
+ minHeight: 33,
1340
+ padding: "2px 0",
1341
+ borderTop: `1px solid ${C.border1}`
1342
+ };
1343
+ const rowKeyStyle = { width: 62, flex: "0 0 62px", fontSize: 11.5, color: C.label3 };
1344
+ const rowValStyle = { display: "flex", alignItems: "center", gap: 4, flexWrap: "wrap", minWidth: 0 };
822
1345
  const inputStyle = {
823
- width: "100%",
824
1346
  boxSizing: "border-box",
825
- padding: "5px 8px",
826
- borderRadius: 8,
1347
+ width: 88,
1348
+ minHeight: 28,
1349
+ padding: "4px 8px",
1350
+ borderRadius: 7,
827
1351
  border: `1px solid ${C.border}`,
828
1352
  background: C.layer2,
829
1353
  color: C.label,
830
- fontSize: 13,
1354
+ fontSize: 12.5,
831
1355
  fontVariantNumeric: "tabular-nums",
832
1356
  outline: "none"
833
1357
  };
834
- const labelStyle = { fontSize: 11, color: C.label3 };
835
- const textField = (field, labelKey, placeholder) => import_react.default.createElement("label", {
836
- key: field,
837
- style: { display: "flex", flexDirection: "column", gap: 3, flex: "1 1 130px", minWidth: 112 }
1358
+ const selectStyle = {
1359
+ boxSizing: "border-box",
1360
+ width: 48,
1361
+ minHeight: 28,
1362
+ padding: "4px 2px 4px 6px",
1363
+ borderRadius: 7,
1364
+ border: `1px solid ${C.border}`,
1365
+ background: C.layer2,
1366
+ color: C.label,
1367
+ fontSize: 12.5,
1368
+ fontVariantNumeric: "tabular-nums",
1369
+ outline: "none",
1370
+ cursor: "pointer"
1371
+ };
1372
+ const unitStyle = { fontSize: 10.5, color: C.label3, whiteSpace: "nowrap" };
1373
+ const dashStyle = { fontSize: 11, color: C.label3, margin: "0 1px" };
1374
+ const HOUR_OPTIONS = Array.from({ length: 24 }, (_, i) => String(i).padStart(2, "0"));
1375
+ const MINUTE_OPTIONS = Array.from({ length: 60 }, (_, i) => String(i).padStart(2, "0"));
1376
+ const row = (key, label, values) => import_react.default.createElement("div", { key, style: rowStyle }, [
1377
+ import_react.default.createElement("span", { key: "k", style: rowKeyStyle }, label),
1378
+ import_react.default.createElement("div", { key: "v", style: rowValStyle }, values)
1379
+ ]);
1380
+ const timeSelects = (field, label, disabled = false) => {
1381
+ const [rawHour = "00", rawMinute = "00"] = String(form[field] || "00:00").split(":");
1382
+ const hour = HOUR_OPTIONS.includes(rawHour) ? rawHour : "00";
1383
+ const minute = MINUTE_OPTIONS.includes(rawMinute) ? rawMinute : "00";
1384
+ const commit = (nextHour, nextMinute) => setField(field, `${nextHour}:${nextMinute}`);
1385
+ const style = { ...selectStyle, opacity: disabled ? 0.45 : 1, cursor: disabled ? "default" : "pointer" };
1386
+ return import_react.default.createElement("span", { key: field, style: { display: "inline-flex", alignItems: "center", gap: 2 } }, [
1387
+ import_react.default.createElement("select", {
1388
+ key: "h",
1389
+ value: hour,
1390
+ disabled,
1391
+ "aria-label": `${label} ${t("hourUnit")}`,
1392
+ onChange: (event) => commit(event.target.value, minute),
1393
+ style
1394
+ }, HOUR_OPTIONS.map((value) => import_react.default.createElement("option", { key: value, value }, value))),
1395
+ import_react.default.createElement("span", { key: "c", style: { color: C.label3, fontWeight: 700, fontSize: 12 } }, ":"),
1396
+ import_react.default.createElement("select", {
1397
+ key: "m",
1398
+ value: minute,
1399
+ disabled,
1400
+ "aria-label": `${label} ${t("minuteUnit")}`,
1401
+ onChange: (event) => commit(hour, event.target.value),
1402
+ style
1403
+ }, MINUTE_OPTIONS.map((value) => import_react.default.createElement("option", { key: value, value }, value)))
1404
+ ]);
1405
+ };
1406
+ const timeRange = (startField, endField, label, disabled) => import_react.default.createElement("span", {
1407
+ key: `${startField}-${endField}`,
1408
+ style: { display: "inline-flex", alignItems: "center", gap: 2 }
838
1409
  }, [
839
- import_react.default.createElement("span", { key: "l", style: labelStyle }, t(labelKey)),
840
- import_react.default.createElement("input", {
841
- key: "i",
842
- type: "text",
843
- value: form[field],
844
- placeholder: placeholder || "HH:mm",
845
- onChange: (event) => setField(field, event.target.value),
846
- style: inputStyle
847
- })
1410
+ timeSelects(startField, label, disabled),
1411
+ import_react.default.createElement("span", { key: "dash", style: dashStyle }, "\u2013"),
1412
+ timeSelects(endField, label, disabled)
848
1413
  ]);
849
- const checkField = (field, labelKey) => import_react.default.createElement("label", {
1414
+ const toggle = (field, labelKey, disabled) => import_react.default.createElement("label", {
850
1415
  key: field,
851
1416
  style: {
852
- display: "flex",
1417
+ display: "inline-flex",
853
1418
  alignItems: "center",
854
- gap: 6,
855
- flex: "1 1 140px",
856
- fontSize: 12,
857
- color: C.label2,
858
- cursor: "pointer",
859
- minHeight: 30
1419
+ gap: 5,
1420
+ padding: "2px 6px",
1421
+ borderRadius: 8,
1422
+ border: `1px solid ${C.border1}`,
1423
+ background: C.layer2,
1424
+ fontSize: 10.5,
1425
+ color: disabled ? C.label3 : C.label2,
1426
+ whiteSpace: "nowrap",
1427
+ cursor: disabled ? "default" : "pointer",
1428
+ opacity: disabled ? 0.55 : 1
860
1429
  }
861
1430
  }, [
862
1431
  import_react.default.createElement("input", {
863
1432
  key: "i",
864
1433
  type: "checkbox",
865
1434
  checked: Boolean(form[field]),
866
- onChange: (event) => setCheck(field, event.target.checked)
1435
+ disabled,
1436
+ onChange: (event) => setCheck(field, event.target.checked),
1437
+ style: { accentColor: GOLD_TINT, width: 13, height: 13, margin: 0, cursor: "inherit" }
867
1438
  }),
868
1439
  import_react.default.createElement("span", { key: "l" }, t(labelKey))
869
1440
  ]);
870
- const textareaField = (field, labelKey) => import_react.default.createElement("label", {
871
- key: field,
872
- style: { display: "flex", flexDirection: "column", gap: 3, flex: "1 1 220px", minWidth: 200 }
1441
+ const lunchDisabled = !form.lunchEnabled;
1442
+ return import_react.default.createElement("details", {
1443
+ open: true,
1444
+ style: { background: C.layer1, border: `1px solid ${C.border1}`, borderRadius: 12, padding: "10px 12px" }
873
1445
  }, [
874
- import_react.default.createElement("span", { key: "l", style: labelStyle }, t(labelKey)),
875
- import_react.default.createElement("textarea", {
876
- key: "i",
877
- rows: 4,
878
- value: form[field],
879
- spellCheck: false,
880
- onChange: (event) => setField(field, event.target.value),
881
- style: {
882
- ...inputStyle,
883
- resize: "vertical",
884
- fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
885
- lineHeight: 1.5
886
- }
887
- })
888
- ]);
889
- return import_react.default.createElement("details", { style: { ...CARD, padding: "10px 12px" } }, [
890
1446
  import_react.default.createElement("summary", {
891
1447
  key: "sum",
892
- style: { cursor: "pointer", fontSize: 13, fontWeight: 600, color: C.label, userSelect: "none" }
1448
+ style: { cursor: "pointer", fontSize: 12.5, fontWeight: 650, color: C.label, userSelect: "none" }
893
1449
  }, t("settings")),
894
- import_react.default.createElement("div", {
895
- key: "hint",
896
- style: { fontSize: 11, color: C.label3, margin: "4px 0 9px" }
897
- }, t("settingsHint")),
898
- import_react.default.createElement("div", {
899
- key: "fields",
900
- style: { display: "flex", flexWrap: "wrap", gap: 8 }
901
- }, [
902
- textField("monthlySalary", "salaryField", "10000"),
903
- textField("workStart", "workStartField", "09:00"),
904
- textField("workEnd", "workEndField", "18:00"),
905
- textField("lunchStart", "lunchStartField", "12:00"),
906
- textField("lunchEnd", "lunchEndField", "13:00"),
907
- checkField("lunchEnabled", "lunchEnabledField"),
908
- checkField("lunchPaid", "lunchPaidField")
909
- ]),
910
- import_react.default.createElement("div", {
911
- key: "lists",
912
- style: { display: "flex", flexWrap: "wrap", gap: 8, marginTop: 8 }
913
- }, [
914
- textareaField("holidaysText", "holidaysField"),
915
- textareaField("extraWorkdaysText", "extraWorkdaysField")
1450
+ import_react.default.createElement("div", { key: "body", style: { marginTop: 7 } }, [
1451
+ row("salary", t("salaryField"), [
1452
+ import_react.default.createElement("input", {
1453
+ key: "i",
1454
+ type: "text",
1455
+ inputMode: "decimal",
1456
+ value: form.monthlySalary,
1457
+ placeholder: "10000",
1458
+ onChange: (event) => setField("monthlySalary", event.target.value),
1459
+ style: inputStyle
1460
+ }),
1461
+ import_react.default.createElement("span", { key: "u", style: unitStyle }, t("salaryUnit"))
1462
+ ]),
1463
+ row("worktime", t("groupWorkTime"), [
1464
+ timeRange("workStart", "workEnd", t("groupWorkTime"), false)
1465
+ ]),
1466
+ row("lunch", t("groupLunch"), [
1467
+ // 时间在前、两个开关在后(放到行尾)
1468
+ timeRange("lunchStart", "lunchEnd", t("groupLunch"), lunchDisabled),
1469
+ toggle("lunchEnabled", "lunchEnabledShort", false),
1470
+ toggle("lunchPaid", "lunchPaidShort", lunchDisabled)
1471
+ ])
916
1472
  ]),
917
1473
  import_react.default.createElement("div", {
918
1474
  key: "actions",
919
- style: { display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap", marginTop: 10 }
1475
+ style: { display: "flex", alignItems: "center", gap: 10, marginTop: 7, paddingTop: 7, borderTop: `1px solid ${C.border1}` }
920
1476
  }, [
921
1477
  import_react.default.createElement("button", {
922
1478
  key: "save",
@@ -924,20 +1480,24 @@ function SettingsSection(props) {
924
1480
  onClick: onSave,
925
1481
  disabled: save.status === "saving",
926
1482
  style: {
927
- border: `1px solid ${C.brand}`,
928
- color: C.brand,
929
- background: "transparent",
1483
+ border: `1px solid color-mix(in srgb, ${GOLD_TINT} 72%, rgba(0,0,0,.18))`,
1484
+ color: "#3A2A05",
1485
+ background: save.status === "saving" ? `color-mix(in srgb, ${GOLD_TINT} 52%, #fff)` : "linear-gradient(180deg, #F8DC86 0%, #EFC258 52%, #D99D2C 100%)",
930
1486
  borderRadius: 8,
931
- padding: "6px 16px",
932
- fontSize: 13,
933
- fontWeight: 600,
934
- cursor: save.status === "saving" ? "default" : "pointer"
1487
+ padding: "5px 16px",
1488
+ fontSize: 12.5,
1489
+ fontWeight: 700,
1490
+ letterSpacing: ".3px",
1491
+ cursor: save.status === "saving" ? "default" : "pointer",
1492
+ opacity: save.status === "saving" ? 0.72 : 1,
1493
+ boxShadow: `0 2px 9px color-mix(in srgb, ${GOLD_TINT} 34%, transparent), inset 0 1px 0 rgba(255,255,255,.45)`,
1494
+ textShadow: "0 1px 0 rgba(255,255,255,.28)"
935
1495
  }
936
1496
  }, save.status === "saving" ? t("saving") : t("save")),
937
- save.status === "ok" ? import_react.default.createElement("span", { key: "ok", style: { fontSize: 12, color: C.ok } }, save.message || t("saved")) : null,
1497
+ save.status === "ok" ? import_react.default.createElement("span", { key: "ok", style: { fontSize: 11.5, color: C.ok } }, save.message || t("saved")) : null,
938
1498
  save.status === "error" ? import_react.default.createElement("span", {
939
1499
  key: "err",
940
- style: { fontSize: 12, color: C.error, flex: "1 1 220px", whiteSpace: "pre-wrap" }
1500
+ style: { fontSize: 11.5, color: C.error, flex: "1 1 200px", whiteSpace: "pre-wrap" }
941
1501
  }, `${t("saveFailed")}${save.errors.join("\uFF1B")}`) : null
942
1502
  ])
943
1503
  ]);
@@ -959,6 +1519,48 @@ function NiumaPanel(props) {
959
1519
  const snap = import_react.default.useMemo(() => earnedToday(cfg, now), [cfg, now]);
960
1520
  const rates = snap && snap.rates ? snap.rates : computeRates(cfg, snap && snap.dateKey);
961
1521
  const baseForm = import_react.default.useMemo(() => formFromConfig(cfg), [cfg]);
1522
+ const viewYear = Number(String(snap && snap.dateKey ? snap.dateKey : "").slice(0, 4)) || (/* @__PURE__ */ new Date()).getFullYear();
1523
+ const yearEstimated = viewYear === HOLIDAY_ESTIMATE_2027.year;
1524
+ const yearData = import_react.default.useMemo(() => yearSummary(cfg, viewYear), [cfg, viewYear]);
1525
+ const [view, setView] = import_react.default.useState("current");
1526
+ const selectedMonth = view.startsWith("m") ? yearData.months[Number(view.slice(1)) - 1] : null;
1527
+ const maxHolidayMonth = yearData.maxHolidayMonth;
1528
+ const maxHolidayText = t("maxHolidayNote").replace("{month}", String(maxHolidayMonth.month)).replace("{days}", String(maxHolidayMonth.holidayDays));
1529
+ let viewCards;
1530
+ let viewHint;
1531
+ let viewNote;
1532
+ if (view === "year") {
1533
+ viewCards = [
1534
+ { key: "avgSecond", label: t("averageSecond"), value: money(yearData.averageSecondWage, 4), accent: true },
1535
+ { key: "avgMinute", label: t("averageMinute"), value: money(yearData.averageMinuteWage, 4) },
1536
+ { key: "avgHourly", label: t("averageHourly"), value: money(yearData.averageHourlyWage, 2) },
1537
+ { key: "avgDaily", label: t("averageDaily"), value: money(yearData.averageDailyWage, 2) },
1538
+ { key: "yearWorkdays", label: t("yearWorkdays"), value: `${yearData.totalWorkdays}${t("dayUnit")}` },
1539
+ { key: "yearIncome", label: t("yearIncome"), value: money(yearData.annualIncome, 2) }
1540
+ ];
1541
+ viewHint = `${t("yearHint").replace("{year}", String(viewYear)).replace("{workdays}", String(yearData.totalWorkdays))} \xB7 ${t("viewOnlyHint")}` + (yearEstimated ? ` \xB7 ${t("holidayEstimateShort")}` : "");
1542
+ viewNote = yearEstimated ? t("holidayEstimateNote") : maxHolidayText;
1543
+ } else if (selectedMonth) {
1544
+ viewCards = [
1545
+ { key: "secondWage", label: t("secondWage"), value: money(selectedMonth.secondWage, 4), accent: true },
1546
+ { key: "minuteWage", label: t("minuteWage"), value: money(selectedMonth.minuteWage, 4) },
1547
+ { key: "hourlyWage", label: t("hourlyWage"), value: money(selectedMonth.hourlyWage, 2) },
1548
+ { key: "dailyWage", label: t("dailyWage"), value: money(selectedMonth.dailyWage, 2) },
1549
+ { key: "monthlyWage", label: t("monthlyWage"), value: money(selectedMonth.monthlySalary, 2) },
1550
+ { key: "monthWorkdays", label: t("monthWorkdaysCard"), value: `${selectedMonth.workdays}${t("dayUnit")}` }
1551
+ ];
1552
+ viewHint = `${t("monthHint").replace("{year}", String(viewYear)).replace("{month}", String(selectedMonth.month)).replace("{workdays}", String(selectedMonth.workdays)).replace("{holidays}", String(selectedMonth.holidayDays))} \xB7 ${t("viewOnlyHint")}` + (yearEstimated ? ` \xB7 ${t("holidayEstimateShort")}` : "");
1553
+ viewNote = yearEstimated ? t("holidayEstimateNote") : selectedMonth.month === maxHolidayMonth.month ? `\u2605 ${t("maxHolidayThis").replace("{days}", String(maxHolidayMonth.holidayDays))}` : maxHolidayText;
1554
+ } else {
1555
+ viewCards = RATE_CARDS.map((spec) => ({
1556
+ key: spec.key,
1557
+ label: t(spec.label),
1558
+ value: money(rates[spec.key], spec.digits),
1559
+ accent: spec.key === "secondWage"
1560
+ }));
1561
+ viewHint = t("currentHint").replace("{days}", String(rates.monthlyWorkdays)).replace("{hours}", trimNumber(rates.paidHours, 2));
1562
+ viewNote = maxHolidayText;
1563
+ }
962
1564
  const [draft, setDraft] = import_react.default.useState(null);
963
1565
  const [save, setSave] = import_react.default.useState({ status: "idle", errors: [], message: "" });
964
1566
  const form = draft || baseForm;
@@ -1011,79 +1613,166 @@ function NiumaPanel(props) {
1011
1613
  } : remote.status === "loading" ? { color: C.label3, text: t("configLoading"), retry: false } : null;
1012
1614
  return import_react.default.createElement("div", {
1013
1615
  style: {
1014
- display: "flex",
1015
- flexDirection: "column",
1016
- gap: 9,
1017
- minWidth: 0,
1018
- padding: "2px 1px 18px",
1616
+ // DSH 的 main slot 给的是定高容器:插件必须自己当滚动容器,否则展开设置后底部会被裁掉。
1617
+ // 注意:这里用 block + 内层 flex 的写法 —— 如果根直接 display:flex,子项会在定高容器里
1618
+ // 被 flex-shrink 压扁(展开设置时整张主卡高度被压缩、内容被 overflow 裁掉)。
1619
+ height: "100%",
1620
+ width: "100%",
1621
+ overflowY: "auto",
1622
+ overflowX: "hidden",
1623
+ overscrollBehavior: "contain",
1624
+ scrollbarGutter: "stable",
1625
+ boxSizing: "border-box",
1019
1626
  fontSize: 13,
1020
1627
  lineHeight: 1.45,
1021
1628
  color: C.label,
1022
1629
  fontFamily: "inherit"
1023
1630
  }
1024
1631
  }, [
1025
- // 标题 + 状态徽标
1632
+ import_react.default.createElement(StyleTag, { key: "css" }),
1026
1633
  import_react.default.createElement("div", {
1027
- key: "head",
1028
- style: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }
1029
- }, [
1030
- import_react.default.createElement("div", { key: "tt", style: { marginRight: "auto", minWidth: 0 } }, [
1031
- import_react.default.createElement("div", {
1032
- key: "t",
1033
- style: { fontSize: 16, fontWeight: 700, color: C.label }
1034
- }, t("title")),
1035
- import_react.default.createElement("div", {
1036
- key: "s",
1037
- style: { fontSize: 11, color: C.label3 }
1038
- }, t("subtitle"))
1039
- ]),
1040
- import_react.default.createElement(StatusBadge, { key: "badge", t, status: snap.status })
1041
- ]),
1042
- // 配置读取状态
1043
- banner ? import_react.default.createElement("div", {
1044
- key: "banner",
1634
+ key: "inner",
1045
1635
  style: {
1046
1636
  display: "flex",
1047
- alignItems: "center",
1048
- gap: 8,
1049
- flexWrap: "wrap",
1050
- fontSize: 12,
1051
- color: banner.color,
1052
- padding: "5px 9px",
1053
- borderRadius: 8,
1054
- border: `1px solid ${banner.color}`,
1055
- background: C.layer1
1637
+ flexDirection: "column",
1638
+ gap: 11,
1639
+ width: "100%",
1640
+ maxWidth: 820,
1641
+ margin: "0 auto",
1642
+ padding: "16px 18px 40px",
1643
+ boxSizing: "border-box",
1644
+ minWidth: 0
1056
1645
  }
1057
1646
  }, [
1058
- import_react.default.createElement("span", { key: "txt", style: { flex: "1 1 160px" } }, banner.text),
1059
- banner.retry ? import_react.default.createElement("button", {
1060
- key: "retry",
1061
- type: "button",
1062
- onClick: remote.refresh,
1647
+ // 品牌头:图标块 + 标题/副标题 + 状态徽标
1648
+ import_react.default.createElement("div", {
1649
+ key: "head",
1650
+ style: { display: "flex", alignItems: "center", gap: 11, flexWrap: "wrap" }
1651
+ }, [
1652
+ import_react.default.createElement("div", {
1653
+ key: "icon",
1654
+ style: {
1655
+ width: 38,
1656
+ height: 38,
1657
+ borderRadius: 12,
1658
+ flex: "0 0 auto",
1659
+ display: "grid",
1660
+ placeItems: "center",
1661
+ color: C.brand,
1662
+ background: `color-mix(in srgb, ${C.brand} 13%, transparent)`,
1663
+ border: `1px solid color-mix(in srgb, ${C.brand} 26%, transparent)`
1664
+ }
1665
+ }, import_react.default.createElement(NiumaIcon, { size: 21 })),
1666
+ import_react.default.createElement("div", { key: "tt", style: { marginRight: "auto", minWidth: 0 } }, [
1667
+ import_react.default.createElement("div", {
1668
+ key: "t",
1669
+ style: { fontSize: 17.5, fontWeight: 800, letterSpacing: "-.3px", color: C.label }
1670
+ }, t("title")),
1671
+ import_react.default.createElement("div", {
1672
+ key: "s",
1673
+ style: { fontSize: 11, color: C.label3, marginTop: 1 }
1674
+ }, t("subtitle"))
1675
+ ]),
1676
+ import_react.default.createElement(StatusBadge, { key: "badge", t, status: snap.status })
1677
+ ]),
1678
+ // 配置读取状态
1679
+ banner ? import_react.default.createElement("div", {
1680
+ key: "banner",
1063
1681
  style: {
1064
- border: `1px solid ${banner.color}`,
1065
- color: banner.color,
1066
- background: "transparent",
1067
- borderRadius: 6,
1068
- padding: "2px 10px",
1682
+ display: "flex",
1683
+ alignItems: "center",
1684
+ gap: 8,
1685
+ flexWrap: "wrap",
1069
1686
  fontSize: 12,
1070
- cursor: "pointer"
1687
+ color: banner.color,
1688
+ padding: "6px 10px",
1689
+ borderRadius: 9,
1690
+ border: `1px solid color-mix(in srgb, ${banner.color} 45%, transparent)`,
1691
+ background: `color-mix(in srgb, ${banner.color} 10%, transparent)`
1071
1692
  }
1072
- }, t("retry")) : null
1073
- ]) : null,
1074
- import_react.default.createElement(HeroCard, { key: "hero", t, snap }),
1075
- import_react.default.createElement(ProgressCard, { key: "progress", t, snap }),
1076
- import_react.default.createElement(RatesGrid, { key: "rates", t, rates }),
1077
- import_react.default.createElement(DetailsFooter, { key: "footer", t, snap, holidaySource }),
1078
- import_react.default.createElement(SettingsSection, {
1079
- key: "settings",
1080
- t,
1081
- form,
1082
- setField,
1083
- setCheck,
1084
- save,
1085
- onSave
1086
- })
1693
+ }, [
1694
+ import_react.default.createElement("span", { key: "txt", style: { flex: "1 1 160px" } }, banner.text),
1695
+ banner.retry ? import_react.default.createElement("button", {
1696
+ key: "retry",
1697
+ type: "button",
1698
+ onClick: remote.refresh,
1699
+ style: {
1700
+ border: `1px solid ${banner.color}`,
1701
+ color: banner.color,
1702
+ background: "transparent",
1703
+ borderRadius: 6,
1704
+ padding: "2px 10px",
1705
+ fontSize: 12,
1706
+ cursor: "pointer"
1707
+ }
1708
+ }, t("retry")) : null
1709
+ ]) : null,
1710
+ import_react.default.createElement(HeroCard, { key: "hero", t, snap }),
1711
+ import_react.default.createElement(CountdownRow, { key: "countdown", t, snap }),
1712
+ import_react.default.createElement("div", {
1713
+ key: "ratesHead",
1714
+ style: {
1715
+ display: "flex",
1716
+ alignItems: "center",
1717
+ justifyContent: "space-between",
1718
+ gap: 8,
1719
+ flexWrap: "wrap",
1720
+ margin: "2px 1px -1px"
1721
+ }
1722
+ }, [
1723
+ import_react.default.createElement("span", {
1724
+ key: "t",
1725
+ style: { fontSize: 12.5, fontWeight: 700, color: C.label2, letterSpacing: ".2px" }
1726
+ }, t("sectionRates")),
1727
+ import_react.default.createElement(PeriodSelect, {
1728
+ key: "period",
1729
+ t,
1730
+ value: view,
1731
+ onChange: setView,
1732
+ year: viewYear,
1733
+ months: yearData.months
1734
+ })
1735
+ ]),
1736
+ import_react.default.createElement("div", {
1737
+ key: "ratesHint",
1738
+ style: { fontSize: 10.5, color: C.label3, ...NUM }
1739
+ }, viewHint),
1740
+ import_react.default.createElement(RatesGrid, { key: "rates", cards: viewCards }),
1741
+ viewNote ? import_react.default.createElement("div", {
1742
+ key: "ratesNote",
1743
+ style: { fontSize: 10.5, color: C.label3, display: "flex", alignItems: "center", gap: 6, flexWrap: "wrap" }
1744
+ }, [
1745
+ import_react.default.createElement("span", {
1746
+ key: "badge",
1747
+ style: {
1748
+ color: C.label2,
1749
+ fontWeight: 600,
1750
+ background: `color-mix(in srgb, ${GOLD_TINT} 14%, transparent)`,
1751
+ border: `1px solid color-mix(in srgb, ${GOLD_TINT} 32%, transparent)`,
1752
+ borderRadius: 999,
1753
+ padding: "1px 8px"
1754
+ }
1755
+ }, t("maxHolidayBadge")),
1756
+ import_react.default.createElement("span", { key: "txt" }, viewNote)
1757
+ ]) : null,
1758
+ import_react.default.createElement(SectionTitle, { key: "detailTitle", title: t("sectionDetail") }),
1759
+ import_react.default.createElement(DetailsFooter, { key: "footer", t, snap, holidaySource }),
1760
+ import_react.default.createElement(HolidaysCard, {
1761
+ key: "holidays",
1762
+ t,
1763
+ holidaySource,
1764
+ extraWorkdays: cfg.extraWorkdays
1765
+ }),
1766
+ import_react.default.createElement(SettingsSection, {
1767
+ key: "settings",
1768
+ t,
1769
+ form,
1770
+ setField,
1771
+ setCheck,
1772
+ save,
1773
+ onSave
1774
+ })
1775
+ ])
1087
1776
  ]);
1088
1777
  }
1089
1778
  var inject = ["slots", "locale"];