dsh-piggy 0.25.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 (104) hide show
  1. package/CHANGELOG.md +1348 -0
  2. package/LICENSE +32 -0
  3. package/README.md +322 -0
  4. package/THIRD-PARTY.md +31 -0
  5. package/assets/elder.svg +35 -0
  6. package/assets/pig-king-bathe.svg +38 -0
  7. package/assets/pig-king-eat.svg +45 -0
  8. package/assets/pig-king-pet.svg +45 -0
  9. package/assets/pig-king-play.svg +45 -0
  10. package/assets/pig-king-relaxed.svg +56 -0
  11. package/assets/pig-king-study.svg +45 -0
  12. package/assets/pig-king-trip.svg +45 -0
  13. package/assets/pig-king-work.svg +45 -0
  14. package/assets/pig-king.svg +56 -0
  15. package/assets/piglet.svg +30 -0
  16. package/client.js +3438 -0
  17. package/commands.js +252 -0
  18. package/cordis.patch.yml +15 -0
  19. package/core.js +11 -0
  20. package/data.js +10 -0
  21. package/index.js +68 -0
  22. package/package.json +72 -0
  23. package/packages/pet-core/package.json +21 -0
  24. package/packages/pet-core/src/core/activity.js +80 -0
  25. package/packages/pet-core/src/core/care.js +160 -0
  26. package/packages/pet-core/src/core/clock.js +122 -0
  27. package/packages/pet-core/src/core/constants.js +109 -0
  28. package/packages/pet-core/src/core/daily.js +204 -0
  29. package/packages/pet-core/src/core/diary.js +108 -0
  30. package/packages/pet-core/src/core/drops.js +82 -0
  31. package/packages/pet-core/src/core/effects.js +84 -0
  32. package/packages/pet-core/src/core/egg.js +120 -0
  33. package/packages/pet-core/src/core/evolution.js +90 -0
  34. package/packages/pet-core/src/core/growth.js +110 -0
  35. package/packages/pet-core/src/core/illness.js +235 -0
  36. package/packages/pet-core/src/core/interests.js +55 -0
  37. package/packages/pet-core/src/core/inventory.js +177 -0
  38. package/packages/pet-core/src/core/lines.js +157 -0
  39. package/packages/pet-core/src/core/migrate.js +226 -0
  40. package/packages/pet-core/src/core/profile.js +102 -0
  41. package/packages/pet-core/src/core/random.js +79 -0
  42. package/packages/pet-core/src/core/school.js +81 -0
  43. package/packages/pet-core/src/core/settlement.js +308 -0
  44. package/packages/pet-core/src/core/state.js +210 -0
  45. package/packages/pet-core/src/core/travel.js +58 -0
  46. package/packages/pet-core/src/core/upgrades.js +193 -0
  47. package/packages/pet-core/src/core/views.js +55 -0
  48. package/packages/pet-core/src/core/work.js +50 -0
  49. package/packages/pet-core/src/core.js +95 -0
  50. package/packages/pet-core/src/data/daily.js +121 -0
  51. package/packages/pet-core/src/data/drops.js +34 -0
  52. package/packages/pet-core/src/data/evolution.js +41 -0
  53. package/packages/pet-core/src/data/growth.js +72 -0
  54. package/packages/pet-core/src/data/illness.js +188 -0
  55. package/packages/pet-core/src/data/interests.js +39 -0
  56. package/packages/pet-core/src/data/jobs.js +150 -0
  57. package/packages/pet-core/src/data/life.js +88 -0
  58. package/packages/pet-core/src/data/lines.js +198 -0
  59. package/packages/pet-core/src/data/minutes.js +23 -0
  60. package/packages/pet-core/src/data/profile.js +60 -0
  61. package/packages/pet-core/src/data/school.js +99 -0
  62. package/packages/pet-core/src/data/shop.js +142 -0
  63. package/packages/pet-core/src/data/traits.js +42 -0
  64. package/packages/pet-core/src/data/travel.js +87 -0
  65. package/packages/pet-core/src/data.js +52 -0
  66. package/packages/pet-core/src/index.js +12 -0
  67. package/render.js +357 -0
  68. package/routes.js +201 -0
  69. package/scripts/build-client.mjs +100 -0
  70. package/scripts/typecheck.mjs +122 -0
  71. package/snapshot.js +368 -0
  72. package/src/client/art.js +29 -0
  73. package/src/client/constants.js +88 -0
  74. package/src/client/css-base.js +225 -0
  75. package/src/client/css-card.js +63 -0
  76. package/src/client/css-tabs.js +250 -0
  77. package/src/client/css-tiles.js +89 -0
  78. package/src/client/dom.js +33 -0
  79. package/src/client/effects.js +159 -0
  80. package/src/client/format.js +27 -0
  81. package/src/client/index.js +393 -0
  82. package/src/client/io.js +100 -0
  83. package/src/client/layout.js +103 -0
  84. package/src/client/normalize.js +352 -0
  85. package/src/client/panel.js +319 -0
  86. package/src/client/scene.js +113 -0
  87. package/src/client/storage.js +19 -0
  88. package/src/client/styles.js +13 -0
  89. package/src/client/tabs/bag.js +203 -0
  90. package/src/client/tabs/card.js +110 -0
  91. package/src/client/tabs/crown.js +59 -0
  92. package/src/client/tabs/dev.js +101 -0
  93. package/src/client/tabs/home.js +72 -0
  94. package/src/client/tabs/shop.js +78 -0
  95. package/src/client/tabs/status.js +223 -0
  96. package/src/client/tabs/study.js +128 -0
  97. package/src/client/tabs/travel.js +89 -0
  98. package/src/client/tabs/update.js +164 -0
  99. package/src/client/tabs/work.js +106 -0
  100. package/src/client/values.js +11 -0
  101. package/src/client/widgets.js +126 -0
  102. package/store/api.js +260 -0
  103. package/store/state-file.js +129 -0
  104. package/store.js +96 -0
package/client.js ADDED
@@ -0,0 +1,3438 @@
1
+ // GENERATED FILE. Edit src/client/ and run `npm run build`; do not edit by hand.
2
+ (() => {
3
+ // src/client/constants.js
4
+ var STATE_URL = "/dsh-piggy/state";
5
+ var ART_URL = "/dsh-piggy/art/";
6
+ var ACT_URL = "/dsh-piggy/act";
7
+ var POLL_MS = 4e3;
8
+ var IDLE_CHAT_MINUTES = { min: 20, max: 40 };
9
+ var GREET_DELAY_MS = 1500;
10
+ var MOUNTED = "data-dsh-pig";
11
+ var OPEN_KEY = "dsh-piggy:open";
12
+ var POSITION_KEY = "dsh-piggy:position";
13
+ var PANEL_WIDTH = 292;
14
+ var PANEL_GAP = 8;
15
+ var PANEL_MARGIN = 10;
16
+ var PANEL_MIN_HEIGHT = 120;
17
+ var SCENE_RESERVE = 132;
18
+ var PIG_PADDING_X = 6;
19
+ var TABS = [
20
+ { key: "status", label: "\u72B6\u6001", emoji: "\u{1F4CB}" },
21
+ { key: "card", label: "\u5C45\u6C11\u5361", emoji: "\u{1FAAA}" },
22
+ { key: "crown", label: "\u52A0\u5195", emoji: "\u{1F451}" },
23
+ { key: "study", label: "\u5B66\u4E60", emoji: "\u{1F4DA}" },
24
+ { key: "work", label: "\u6253\u5DE5", emoji: "\u{1F4BC}" },
25
+ { key: "shop", label: "\u5546\u5E97", emoji: "\u{1F6D2}" },
26
+ { key: "travel", label: "\u65C5\u884C", emoji: "\u{1F9F3}" },
27
+ { key: "bag", label: "\u80CC\u5305", emoji: "\u{1F392}" }
28
+ ];
29
+ var DEV_KEY = "dsh-piggy:dev";
30
+ var DEV_TAB = { key: "dev", label: "\u8C03\u8BD5", emoji: "\u{1F527}" };
31
+ var UPDATE_TAB = { key: "update", label: "\u66F4\u65B0", emoji: "\u{1F504}" };
32
+ var PET_LINES = [
33
+ "\u597D\u8212\u670D\u2026",
34
+ "\u518D\u6478\u6478\uFF5E",
35
+ "\u563F\u563F",
36
+ "\u547C\u565C\u547C\u565C\u2026",
37
+ "\u8FD9\u91CC\u8FD9\u91CC\uFF01",
38
+ "\uFF08\u772F\u8D77\u773C\u775B\uFF09",
39
+ "\u4ECA\u5929\u5FC3\u60C5\u4E0D\u9519",
40
+ "\u5514\u2026\u597D\u75D2",
41
+ "\u4F60\u5728\u5FD9\u4EC0\u4E48\u5440",
42
+ "\u518D\u591A\u5F85\u4E00\u4F1A\u513F"
43
+ ];
44
+ var MODES = ["feed", "bathe", "play", "pet"];
45
+ var CARE_LABEL = { feed: ["\u5582\u98DF", "\u{1F34E}"], bathe: ["\u6D17\u6FA1", "\u{1F6C1}"], play: ["\u73A9\u800D", "\u{1F3BE}"], pet: ["\u6478\u6478", "\u2764\uFE0F"] };
46
+ var BOX_POKES_TO_OPEN = 3;
47
+ var BOX_POKE_LINES = [
48
+ "\u91CC\u9762\u597D\u50CF\u6709\u4E1C\u897F\u2026",
49
+ "\u52A8\u4E86\uFF01\u518D\u6233\u4E00\u4E0B\uFF01"
50
+ ];
51
+ var NO_ITEM_LINE = {
52
+ food: "\u6CA1\u6709\u5403\u7684\u5566\uFF0C\u5FEB\u53BB\u4E70\u4E00\u70B9 \u{1F34E}",
53
+ bath: "\u6CA1\u6709\u6D17\u6D74\u7528\u54C1\u4E86\uFF0C\u53BB\u4E70\u70B9\u5427 \u{1F9FC}",
54
+ toy: "\u6CA1\u6709\u73A9\u5177\u4E86\uFF0C\u53BB\u5546\u5E97\u770B\u770B \u{1FA80}"
55
+ };
56
+ var KIND_TITLE = { food: "\u{1F34E} \u98DF\u7269", bath: "\u{1F9FC} \u6D17\u6D74", toy: "\u{1FA80} \u73A9\u5177", dress: "\u{1F455} \u88C5\u626E", medicine: "\u{1F48A} \u836F\u54C1", revive: "\u2728 \u590D\u6D3B" };
57
+ var KIND_ORDER = ["food", "bath", "toy", "dress", "medicine", "revive"];
58
+ var STAGES = [
59
+ { key: "preschool", label: "\u5E7C\u513F\u56ED" },
60
+ { key: "extracurricular", label: "\u8BFE\u5916" },
61
+ { key: "primary", label: "\u5C0F\u5B66" },
62
+ { key: "middle", label: "\u4E2D\u5B66" },
63
+ { key: "high", label: "\u9AD8\u4E2D" },
64
+ { key: "college", label: "\u5927\u5B66" },
65
+ { key: "graduate", label: "\u7814\u7A76\u751F" }
66
+ ];
67
+
68
+ // src/client/values.js
69
+ var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
70
+ var obj = (v) => isObj(v) ? v : {};
71
+ var arr = (v) => Array.isArray(v) ? v : [];
72
+ var num = (v, dflt) => typeof v === "number" && isFinite(v) ? v : dflt;
73
+ var str = (v, dflt) => typeof v === "string" && v !== "" ? v : dflt;
74
+
75
+ // src/client/dom.js
76
+ function el(tag, className, text) {
77
+ var node = document.createElement(tag);
78
+ if (className) node.className = className;
79
+ if (text !== void 0) node.textContent = text;
80
+ return node;
81
+ }
82
+ function button(className, attrs, onClick) {
83
+ var node = el("button", className);
84
+ node.type = "button";
85
+ for (var key in attrs) node.setAttribute(key, attrs[key]);
86
+ node.addEventListener("click", function(event) {
87
+ event.stopPropagation();
88
+ onClick();
89
+ });
90
+ return node;
91
+ }
92
+ function meter(value, variant) {
93
+ var wrap = el("div", "dp-meter" + (variant ? " " + variant : ""));
94
+ var fill = document.createElement("i");
95
+ fill.style.width = Math.max(0, Math.min(100, num(value, 0))) + "%";
96
+ wrap.appendChild(fill);
97
+ return wrap;
98
+ }
99
+
100
+ // src/client/widgets.js
101
+ function labelledBar(ui, label, value, valueText, variant) {
102
+ var row = el("div", "dp-row");
103
+ row.appendChild(el("span", null, label));
104
+ row.appendChild(el("b", null, valueText));
105
+ ui.content.appendChild(row);
106
+ ui.content.appendChild(meter(value, variant));
107
+ }
108
+ function pickerPanel(ui, action) {
109
+ var wrap = el("div", "dp-pick");
110
+ var asks = { feed: "\u5582\u70B9\u4EC0\u4E48\uFF1F", bathe: "\u7528\u54EA\u4E2A\u6D17\u6FA1\uFF1F", play: "\u62FF\u54EA\u4E2A\u73A9\u5177\uFF1F" };
111
+ wrap.appendChild(el("div", "dp-pick-head", asks[action] ?? "\u7528\u54EA\u4E2A\uFF1F"));
112
+ var list = el("div", "dp-list");
113
+ var shelf = ui.view.care[action] ?? [];
114
+ for (var i = 0; i < shelf.length; i += 1) {
115
+ (function(item) {
116
+ var row = el("div", "dp-item");
117
+ row.appendChild(el("span", null, item.emoji));
118
+ var grow = el("div", "dp-grow");
119
+ grow.appendChild(el("div", null, item.label + (item.default ? "\uFF08\u81EA\u5E26\uFF09" : " \xD7" + num(item.count, 0))));
120
+ grow.appendChild(el("div", "dp-dim", careEffectLine(action, item)));
121
+ row.appendChild(grow);
122
+ var use = button("dp-mini", { "data-care": action + ":" + item.key }, function() {
123
+ ui.picker = null;
124
+ ui.send(action, { item: item.key });
125
+ });
126
+ use.textContent = "\u7528";
127
+ row.appendChild(use);
128
+ list.appendChild(row);
129
+ })(shelf[i]);
130
+ }
131
+ wrap.appendChild(list);
132
+ var cancel = button("dp-cancel", {}, function() {
133
+ ui.picker = null;
134
+ ui.renderContent();
135
+ });
136
+ cancel.textContent = "\u7B97\u4E86";
137
+ wrap.appendChild(cancel);
138
+ return wrap;
139
+ }
140
+ function careEffectLine(action, item) {
141
+ var parts = [];
142
+ if (action === "feed") {
143
+ parts.push("\u9971\u98DF +" + item.satiety);
144
+ if (item.happiness) parts.push("\u5FC3\u60C5 +" + item.happiness);
145
+ } else if (action === "bathe") {
146
+ parts.push("\u6E05\u6D01 +" + item.cleanliness);
147
+ if (item.happiness) parts.push("\u5FC3\u60C5 +" + item.happiness);
148
+ } else {
149
+ parts.push("\u5FC3\u60C5 +" + item.happiness);
150
+ if (item.satiety) parts.push("\u9971\u98DF " + item.satiety);
151
+ }
152
+ return parts.join(" \xB7 ");
153
+ }
154
+ function tileGrid() {
155
+ return el("div", "dp-tiles");
156
+ }
157
+ function tile(spec) {
158
+ var node = button("dp-tile" + (spec.soft ? " dp-tile-soft" : ""), spec.data ?? {}, function() {
159
+ spec.onPick();
160
+ });
161
+ node.setAttribute("data-color", spec.color);
162
+ if (spec.locked) node.setAttribute("data-locked", "true");
163
+ if (spec.dim) node.setAttribute("data-dim", "true");
164
+ if (spec.active) node.setAttribute("data-active", "true");
165
+ if (spec.disabled === true) node.disabled = true;
166
+ var icon = el("span", "dp-tile-icon");
167
+ icon.appendChild(el("span", "dp-tile-e", spec.emoji));
168
+ if (spec.badge) icon.appendChild(el("b", "dp-tile-badge", spec.badge));
169
+ if (spec.tag) icon.appendChild(el("b", "dp-tile-tag", spec.tag));
170
+ node.appendChild(icon);
171
+ node.appendChild(el("span", "dp-tile-n", spec.label));
172
+ if (spec.note) node.appendChild(el("span", "dp-tile-note", spec.note));
173
+ return node;
174
+ }
175
+ function drillTo(ui, tab, key) {
176
+ ui.drill[tab] = key;
177
+ ui.drill.pick = null;
178
+ ui.renderContent();
179
+ ui.content.scrollTop = 0;
180
+ }
181
+ function drillHeader(ui, tab, title, info) {
182
+ var row = el("div", "dp-drill");
183
+ var back = button("dp-drill-back", { "data-back": tab }, function() {
184
+ drillTo(ui, tab, null);
185
+ });
186
+ back.textContent = "\u2039";
187
+ row.appendChild(back);
188
+ row.appendChild(el("b", "dp-drill-title", title));
189
+ if (info) row.appendChild(el("span", "dp-drill-info", info));
190
+ ui.content.appendChild(row);
191
+ }
192
+
193
+ // src/client/tabs/shop.js
194
+ var SHELF_COLOR = { food: "red", bath: "teal", toy: "yellow", dress: "pink", medicine: "green", revive: "purple" };
195
+ function shelfParts(kind) {
196
+ var title = KIND_TITLE[kind] ?? kind;
197
+ var space = title.indexOf(" ");
198
+ return space < 0 ? ["\u{1F6D2}", title] : [title.slice(0, space), title.slice(space + 1)];
199
+ }
200
+ function renderShopTab(ui) {
201
+ if (ui.view.shop.length === 0) {
202
+ ui.content.appendChild(el("div", "dp-empty", "\u5BBF\u4E3B\u8FD8\u6CA1\u63D0\u4F9B\u8D27\u67B6\u3002"));
203
+ return;
204
+ }
205
+ var coins = "\u{1FA99} " + ui.view.pig.coins;
206
+ var shelf = ui.drill.shop;
207
+ if (shelf === null || KIND_ORDER.indexOf(shelf) < 0) {
208
+ renderShelves(ui);
209
+ return;
210
+ }
211
+ var parts = shelfParts(shelf);
212
+ drillHeader(ui, "shop", parts[0] + " " + parts[1], coins);
213
+ var grid = tileGrid();
214
+ var items = ui.view.shop.filter(function(item) {
215
+ return item.kind === shelf;
216
+ });
217
+ for (var i = 0; i < items.length; i += 1) grid.appendChild(itemTile(ui, items[i], SHELF_COLOR[shelf]));
218
+ ui.content.appendChild(grid);
219
+ }
220
+ function renderShelves(ui) {
221
+ var grid = tileGrid();
222
+ for (var k = 0; k < KIND_ORDER.length; k += 1) {
223
+ (function(kind) {
224
+ var items = ui.view.shop.filter(function(item) {
225
+ return item.kind === kind;
226
+ });
227
+ if (items.length === 0) return;
228
+ var parts = shelfParts(kind);
229
+ var needed = items.some(function(item) {
230
+ return item.needed;
231
+ });
232
+ grid.appendChild(tile({
233
+ emoji: parts[0],
234
+ label: parts[1],
235
+ color: SHELF_COLOR[kind] ?? "blue",
236
+ tag: needed ? "\u9700\u8981" : "",
237
+ data: { "data-shelf": kind },
238
+ onPick: function() {
239
+ drillTo(ui, "shop", kind);
240
+ }
241
+ }));
242
+ })(KIND_ORDER[k]);
243
+ }
244
+ ui.content.appendChild(grid);
245
+ }
246
+ function itemTile(ui, item, color) {
247
+ var owned = num(ui.view.inventory[item.key], 0);
248
+ var note = item.price + " \u{1FA99}";
249
+ if (item.owned) note = "\u5DF2\u62E5\u6709";
250
+ else if (item.kind === "dress" && item.unlocked === false) note = "\u{1F512} Lv." + item.level;
251
+ return tile({
252
+ emoji: item.emoji,
253
+ label: item.label,
254
+ color,
255
+ soft: true,
256
+ note,
257
+ badge: owned > 0 ? "\xD7" + owned : "",
258
+ tag: item.needed ? "\u9700\u8981" : item.owned && item.worn ? "\u7A7F\u7740" : "",
259
+ dim: !item.owned && (!item.affordable || item.kind === "dress" && item.unlocked === false),
260
+ disabled: item.owned === true,
261
+ data: { "data-buy": item.key },
262
+ onPick: function() {
263
+ ui.send("buy", { item: item.key });
264
+ }
265
+ });
266
+ }
267
+
268
+ // src/client/tabs/bag.js
269
+ var CONSUMABLES = KIND_ORDER.filter(function(kind) {
270
+ return kind !== "dress";
271
+ });
272
+ var EXTRA = {
273
+ dress: { emoji: "\u{1F455}", label: "\u88C5\u626E", color: "pink" },
274
+ diary: { emoji: "\u{1F4D4}", label: "\u65E5\u8BB0", color: "brown" },
275
+ souvenir: { emoji: "\u{1F381}", label: "\u7EAA\u5FF5\u54C1", color: "blue" }
276
+ };
277
+ function shortDay(day) {
278
+ return day.length >= 10 ? day.slice(5) : day;
279
+ }
280
+ function renderBagTab(ui) {
281
+ var open = ui.drill.bag;
282
+ if (open === "dress") renderDress(ui);
283
+ else if (open === "diary") renderDiary(ui);
284
+ else if (open === "souvenir") renderSouvenirs(ui);
285
+ else if (open !== null && CONSUMABLES.indexOf(open) >= 0) renderItems(ui, open);
286
+ else renderCategories(ui);
287
+ }
288
+ function ownedOf(ui, kind) {
289
+ return ui.view.shop.filter(function(item) {
290
+ return item.kind === kind && num(ui.view.inventory[item.key], 0) > 0;
291
+ });
292
+ }
293
+ function renderCategories(ui) {
294
+ var grid = tileGrid();
295
+ for (var k = 0; k < CONSUMABLES.length; k += 1) {
296
+ (function(kind) {
297
+ var items = ownedOf(ui, kind);
298
+ var count = items.reduce(function(sum, item) {
299
+ return sum + num(ui.view.inventory[item.key], 0);
300
+ }, 0);
301
+ var parts = shelfParts(kind);
302
+ grid.appendChild(tile({
303
+ emoji: parts[0],
304
+ label: parts[1],
305
+ color: SHELF_COLOR[kind] ?? "blue",
306
+ badge: count > 0 ? String(count) : "",
307
+ dim: count === 0,
308
+ tag: items.some(function(item) {
309
+ return item.needed;
310
+ }) ? "\u9700\u8981" : "",
311
+ data: { "data-bag": kind },
312
+ onPick: function() {
313
+ drillTo(ui, "bag", kind);
314
+ }
315
+ }));
316
+ })(CONSUMABLES[k]);
317
+ }
318
+ var counts = {
319
+ dress: ui.view.dress.filter(function(item) {
320
+ return item.owned;
321
+ }).length,
322
+ diary: ui.view.diary.length,
323
+ souvenir: ui.view.pig.souvenirs.length
324
+ };
325
+ for (var key in EXTRA) {
326
+ (function(category) {
327
+ var spec = EXTRA[category];
328
+ grid.appendChild(tile({
329
+ emoji: spec.emoji,
330
+ label: spec.label,
331
+ color: spec.color,
332
+ badge: counts[category] > 0 ? String(counts[category]) : "",
333
+ dim: counts[category] === 0,
334
+ data: { "data-bag": category },
335
+ onPick: function() {
336
+ drillTo(ui, "bag", category);
337
+ }
338
+ }));
339
+ })(key);
340
+ }
341
+ ui.content.appendChild(grid);
342
+ }
343
+ function renderItems(ui, kind) {
344
+ var parts = shelfParts(kind);
345
+ drillHeader(ui, "bag", parts[0] + " " + parts[1], "\u70B9\u4E00\u4E0B\u5C31\u7528");
346
+ var items = ownedOf(ui, kind);
347
+ if (items.length === 0) {
348
+ ui.content.appendChild(el("div", "dp-empty", "\u7A7A\u7684"));
349
+ return;
350
+ }
351
+ var grid = tileGrid();
352
+ for (var i = 0; i < items.length; i += 1) {
353
+ (function(item) {
354
+ grid.appendChild(tile({
355
+ emoji: item.emoji,
356
+ label: item.label,
357
+ color: SHELF_COLOR[kind] ?? "blue",
358
+ soft: true,
359
+ badge: "\xD7" + num(ui.view.inventory[item.key], 0),
360
+ tag: item.needed ? "\u9700\u8981" : "",
361
+ data: { "data-use": item.key },
362
+ onPick: function() {
363
+ ui.send("use", { item: item.key });
364
+ }
365
+ }));
366
+ })(items[i]);
367
+ }
368
+ ui.content.appendChild(grid);
369
+ }
370
+ function renderDress(ui) {
371
+ var owned = ui.view.dress.filter(function(item) {
372
+ return item.owned;
373
+ });
374
+ var worn = owned.filter(function(item) {
375
+ return item.worn;
376
+ }).length;
377
+ drillHeader(ui, "bag", "\u{1F455} \u88C5\u626E", worn + " \u4EF6\u7A7F\u7740");
378
+ if (owned.length === 0) {
379
+ ui.content.appendChild(el("div", "dp-empty", "\u8FD8\u6CA1\u6709\u88C5\u626E"));
380
+ return;
381
+ }
382
+ var grid = tileGrid();
383
+ for (var i = 0; i < owned.length; i += 1) {
384
+ (function(item) {
385
+ grid.appendChild(tile({
386
+ emoji: item.emoji,
387
+ label: item.label,
388
+ color: EXTRA.dress.color,
389
+ soft: true,
390
+ note: item.slotLabel,
391
+ tag: item.worn ? "\u7A7F\u7740" : "",
392
+ active: item.worn,
393
+ data: { "data-wear": item.key },
394
+ onPick: function() {
395
+ ui.send("wear", { item: item.key, on: !item.worn });
396
+ }
397
+ }));
398
+ })(owned[i]);
399
+ }
400
+ ui.content.appendChild(grid);
401
+ }
402
+ function renderDiary(ui) {
403
+ drillHeader(ui, "bag", "\u{1F4D4} \u65E5\u8BB0", ui.view.diary.length + " \u7BC7");
404
+ if (ui.view.diary.length === 0) {
405
+ ui.content.appendChild(el("div", "dp-empty", "\u8FD8\u6CA1\u6709\u65E5\u8BB0"));
406
+ return;
407
+ }
408
+ var grid = tileGrid();
409
+ var picked = null;
410
+ for (var d = 0; d < ui.view.diary.length; d += 1) {
411
+ (function(entry) {
412
+ var active = ui.drill.pick === entry.day;
413
+ if (active) picked = entry;
414
+ grid.appendChild(tile({
415
+ emoji: "\u{1F4D4}",
416
+ label: shortDay(entry.day),
417
+ color: EXTRA.diary.color,
418
+ soft: true,
419
+ active,
420
+ data: { "data-diary": entry.day },
421
+ onPick: function() {
422
+ ui.drill.pick = active ? null : entry.day;
423
+ ui.renderContent();
424
+ }
425
+ }));
426
+ })(ui.view.diary[d]);
427
+ }
428
+ ui.content.appendChild(grid);
429
+ if (picked !== null) {
430
+ var page = el("div", "dp-pick dp-tile-card dp-diary-page");
431
+ page.appendChild(el("div", "dp-pick-head", picked.day));
432
+ page.appendChild(el("div", null, picked.text));
433
+ ui.content.appendChild(page);
434
+ }
435
+ }
436
+ function renderSouvenirs(ui) {
437
+ var list = ui.view.pig.souvenirs;
438
+ drillHeader(ui, "bag", "\u{1F381} \u7EAA\u5FF5\u54C1", list.length + " \u4EF6");
439
+ if (list.length === 0) {
440
+ ui.content.appendChild(el("div", "dp-empty", "\u6536\u85CF\u518C\u8FD8\u7A7A\u7740"));
441
+ return;
442
+ }
443
+ var grid = tileGrid();
444
+ var picked = null;
445
+ for (var s = 0; s < list.length; s += 1) {
446
+ (function(entry, index) {
447
+ var id = entry.key + "#" + index;
448
+ var active = ui.drill.pick === id;
449
+ if (active) picked = entry;
450
+ grid.appendChild(tile({
451
+ emoji: entry.emoji,
452
+ label: entry.label,
453
+ color: EXTRA.souvenir.color,
454
+ soft: true,
455
+ active,
456
+ note: entry.rarityEmoji + entry.rarityLabel,
457
+ data: { "data-souvenir": id },
458
+ onPick: function() {
459
+ ui.drill.pick = active ? null : id;
460
+ ui.renderContent();
461
+ }
462
+ }));
463
+ })(list[s], s);
464
+ }
465
+ ui.content.appendChild(grid);
466
+ if (picked === null) return;
467
+ var story = el("div", "dp-pick dp-tile-card");
468
+ story.appendChild(el("div", "dp-pick-head", picked.emoji + " " + picked.label + (picked.fromLabel === "" ? "" : " \xB7 " + picked.fromLabel)));
469
+ story.appendChild(el("div", null, picked.story === "" ? "\uFF08\u65E7\u7248\u672C\u5E26\u56DE\u6765\u7684\uFF0C\u6CA1\u6709\u6545\u4E8B\uFF09" : "\u300C" + picked.story + "\u300D"));
470
+ if (picked.price > 0) {
471
+ var sold = picked;
472
+ var sell = button("dp-mini", { "data-sell": sold.key }, function() {
473
+ ui.drill.pick = null;
474
+ ui.send("sell", { souvenir: sold.key });
475
+ });
476
+ sell.textContent = "\u5356\u6389 +" + sold.price + " \u{1FA99}";
477
+ sell.style.marginTop = "6px";
478
+ story.appendChild(sell);
479
+ }
480
+ ui.content.appendChild(story);
481
+ }
482
+
483
+ // src/client/tabs/dev.js
484
+ function renderDevTab(ui) {
485
+ ui.content.appendChild(el("div", "dp-dev-note", "\u{1F527} \u5F00\u53D1\u8005\u6A21\u5F0F \xB7 \u6784\u5EFA v" + (ui.view.version === "" ? "\u672A\u77E5" : ui.view.version) + " \xB7 Ctrl+Shift+D \u5173\u95ED"));
486
+ if (ui.view.pig !== null && ui.view.pig.ageForced) {
487
+ ui.content.appendChild(el(
488
+ "div",
489
+ "dp-dev-note",
490
+ "\u26A0\uFE0F \u5E74\u9F84\u662F\u8C03\u8BD5\u6539\u7684\uFF08HUD \u4E0A\u6709 \u{1F527}\uFF09\u2014\u2014 \u6309\u300C\u23EA \u5E74\u9F84\u5F52\u96F6\u300D\u624D\u4F1A\u91CD\u65B0\u6309\u771F\u5B9E\u65F6\u95F4\u7B97"
491
+ ));
492
+ }
493
+ var p = ui.view.pig;
494
+ if (p === null) {
495
+ ui.content.appendChild(el("div", "dp-empty", "\u8FD8\u6CA1\u6709\u732A\u3002\u5148\u300C\u62C6\u5F00\u7EB8\u76D2\u300D\u518D\u8C03\u3002"));
496
+ return;
497
+ }
498
+ function group(title, entries) {
499
+ var head = el("div", "dp-title");
500
+ head.appendChild(el("b", null, title));
501
+ ui.content.appendChild(head);
502
+ var wrap = el("div", "dp-dev-row");
503
+ for (var i = 0; i < entries.length; i += 1) {
504
+ (function(entry) {
505
+ var btn = button("dp-mini dp-dev-btn", { "data-dev": entry.key }, function() {
506
+ entry.run();
507
+ });
508
+ btn.textContent = entry.label;
509
+ wrap.appendChild(btn);
510
+ })(entries[i]);
511
+ }
512
+ ui.content.appendChild(wrap);
513
+ }
514
+ var patch = function(body) {
515
+ ui.send("dev", { patch: body });
516
+ };
517
+ group("\u72B6\u6001", [
518
+ { key: "full", label: "\u{1F60A} \u6EE1\u72B6\u6001", run: function() {
519
+ patch({ satiety: 100, happiness: 100, cleanliness: 100, health: 5 });
520
+ } },
521
+ { key: "hungry", label: "\u{1F34E} \u997F", run: function() {
522
+ patch({ satiety: 10 });
523
+ } },
524
+ { key: "dirty", label: "\u{1FAE7} \u810F", run: function() {
525
+ patch({ cleanliness: 10 });
526
+ } },
527
+ { key: "lonely", label: "\u{1F97A} \u5B64\u5355", run: function() {
528
+ patch({ happiness: 10 });
529
+ } },
530
+ { key: "sleepy", label: "\u{1F4A4} \u56F0", run: function() {
531
+ patch({ satiety: 90, happiness: 90, cleanliness: 90 });
532
+ } }
533
+ ]);
534
+ group("\u751F\u75C5", [
535
+ { key: "cold1", label: "\u{1F927} \u611F\u5192", run: function() {
536
+ patch({ illness: { chain: 0, stage: 1 }, health: 4 });
537
+ } },
538
+ { key: "cough1", label: "\u{1F637} \u54B3\u55FD", run: function() {
539
+ patch({ illness: { chain: 1, stage: 1 }, health: 4 });
540
+ } },
541
+ { key: "belly1", label: "\u{1F922} \u809A\u5B50\u80C0", run: function() {
542
+ patch({ illness: { chain: 2, stage: 1 }, health: 4 });
543
+ } },
544
+ { key: "dizzy1", label: "\u{1F635} \u5934\u6655", run: function() {
545
+ patch({ illness: { chain: 3, stage: 1 }, health: 4 });
546
+ } },
547
+ { key: "skin1", label: "\u{1FA79} \u7619\u75D2", run: function() {
548
+ patch({ illness: { chain: 4, stage: 1 }, health: 4 });
549
+ } },
550
+ { key: "cold4", label: "\u2620\uFE0F \u80BA\u708E", run: function() {
551
+ patch({ illness: { chain: 0, stage: 4 }, health: 1 });
552
+ } },
553
+ { key: "cure", label: "\u{1F49A} \u6CBB\u597D", run: function() {
554
+ patch({ illness: null, health: 5 });
555
+ } }
556
+ ]);
557
+ group("\u7B49\u7EA7", [
558
+ { key: "box", label: "\u{1F4E6} \u7EB8\u76D2", run: function() {
559
+ patch({ hatched: false });
560
+ } },
561
+ { key: "lv1", label: "\u5E7C\u5E74 Lv1", run: function() {
562
+ patch({ hatched: true, level: 1 });
563
+ } },
564
+ { key: "lv10", label: "\u9752\u5E74 Lv10", run: function() {
565
+ patch({ level: 10 });
566
+ } },
567
+ { key: "lv40", label: "\u6210\u5E74 Lv40", run: function() {
568
+ patch({ level: 40 });
569
+ } },
570
+ { key: "lv60", label: "\u6EE1\u7EA7 Lv60", run: function() {
571
+ patch({ level: 60 });
572
+ } },
573
+ { key: "real", label: "\u23EA \u5929\u6570\u5F52\u96F6", run: function() {
574
+ ui.send("ageFromNow");
575
+ } }
576
+ ]);
577
+ group("\u8D44\u6E90", [
578
+ { key: "coin100", label: "\u{1FA99} +100", run: function() {
579
+ patch({ coins: p.coins + 100 });
580
+ } },
581
+ { key: "coin999", label: "\u{1FA99} 9999", run: function() {
582
+ patch({ coins: 9999 });
583
+ } },
584
+ { key: "traits", label: "\u{1F9E0}+5 \u2728+5 \u{1F4AA}+5", run: function() {
585
+ patch({ traits: { intel: 5, charm: 5, strong: 5 } });
586
+ } },
587
+ { key: "all", label: "\u{1F381} \u4E00\u952E\u62FF\u9F50", run: function() {
588
+ ui.send("giveAll");
589
+ } }
590
+ ]);
591
+ group("\u65F6\u95F4", [
592
+ { key: "real", label: "\xD71 \u771F\u5B9E", run: function() {
593
+ ui.send("timeScale", { scale: 1 });
594
+ } },
595
+ { key: "fast12", label: "\xD712", run: function() {
596
+ ui.send("timeScale", { scale: 12 });
597
+ } },
598
+ { key: "fast30", label: "\xD730", run: function() {
599
+ ui.send("timeScale", { scale: 30 });
600
+ } },
601
+ { key: "fast60", label: "\xD760", run: function() {
602
+ ui.send("timeScale", { scale: 60 });
603
+ } }
604
+ ]);
605
+ group("\u751F\u6B7B", [
606
+ { key: "kill", label: "\u{1F480} \u5F04\u6B7B", run: function() {
607
+ patch({ dead: true });
608
+ } },
609
+ { key: "revive", label: "\u2728 \u590D\u6D3B", run: function() {
610
+ patch({ dead: false, health: 5 });
611
+ } },
612
+ { key: "adopt", label: "\u{1F4E6} \u9886\u517B", run: function() {
613
+ ui.send("adopt");
614
+ } },
615
+ { key: "reset", label: "\u{1F504} \u91CD\u7F6E", run: function() {
616
+ ui.send("reset");
617
+ } }
618
+ ]);
619
+ group("\u9762\u677F", [
620
+ { key: "open", label: "\u5C55\u5F00/\u6536\u8D77", run: function() {
621
+ ui.setOpen(ui.host.getAttribute("data-open") !== "true");
622
+ } },
623
+ { key: "away1", label: "\u23E9 +1 \u5C0F\u65F6", run: function() {
624
+ patch({ __advanceMs: 36e5 });
625
+ } },
626
+ { key: "away24", label: "\u23E9 +1 \u5929", run: function() {
627
+ patch({ __advanceMs: 864e5 });
628
+ } }
629
+ ]);
630
+ ui.content.appendChild(el(
631
+ "div",
632
+ "dp-dev-note",
633
+ "\u5F53\u524D\uFF1A" + p.stage.label + " \xB7 \u5065\u5EB7 " + p.health + " \xB7 \u{1FA99} " + p.coins + (p.illness === null ? "" : " \xB7 " + p.illness.name)
634
+ ));
635
+ }
636
+
637
+ // src/client/tabs/status.js
638
+ function renderStatusTab(ui) {
639
+ var p = ui.view.pig;
640
+ if (p === null) return;
641
+ renderBanners(ui);
642
+ labelledBar(ui, "\u{1F35A} \u9971\u98DF", p.satiety, p.satiety + "%");
643
+ labelledBar(ui, "\u2764\uFE0F \u5FC3\u60C5", p.happiness, p.happiness + "%", "dp-mood");
644
+ labelledBar(ui, "\u{1FAE7} \u6E05\u6D01", p.cleanliness, p.cleanliness + "%", "dp-clean");
645
+ labelledBar(ui, "\u{1F49A} \u5065\u5EB7", p.healthPercent, p.health + "/" + ui.view.maxHealth, "dp-health");
646
+ var traits = el("div", "dp-traits");
647
+ traits.appendChild(el("span", null, "\u{1F9E0} \u667A\u529B " + p.traits.intel));
648
+ traits.appendChild(el("span", null, "\u2728 \u9B45\u529B " + p.traits.charm));
649
+ traits.appendChild(el("span", null, "\u{1F4AA} \u6B66\u529B " + p.traits.strong));
650
+ ui.content.appendChild(traits);
651
+ var info = el("div", "dp-row");
652
+ info.appendChild(el("span", null, "\u2696\uFE0F \u4F53\u91CD " + p.weight));
653
+ info.appendChild(el("b", null, "\u{1FA99} " + p.coins));
654
+ ui.content.appendChild(info);
655
+ var daily = ui.view.daily;
656
+ var dailyLine = el("div", "dp-row");
657
+ dailyLine.appendChild(el("span", null, "\u{1F4C5} \u7B7E\u5230"));
658
+ dailyLine.appendChild(el("b", null, "\u7B2C " + daily.signInDay + "/" + daily.cycle + " \u5929" + (daily.canSignIn ? " \xB7 \u4ECA\u5929\u8FD8\u6CA1\u7B7E" : "") + (daily.unclaimed > 0 ? " \xB7 \u{1F381} " + daily.unclaimed : "")));
659
+ ui.content.appendChild(dailyLine);
660
+ var lvl = el("div", "dp-row");
661
+ lvl.appendChild(el("span", null, "\u2B50 \u7B49\u7EA7"));
662
+ lvl.appendChild(el("b", null, "Lv." + p.level.level + " " + p.level.titleEmoji + p.level.titleLabel + (p.level.maxed ? " \xB7 \u6EE1\u7EA7" : " \xB7 \u8FD8\u5DEE " + Math.ceil(p.level.toNext) + " \u6210\u957F")));
663
+ ui.content.appendChild(lvl);
664
+ var age = el("div", "dp-row");
665
+ age.appendChild(el("span", null, "\u{1F3E0} \u966A\u4F34"));
666
+ age.appendChild(el("b", null, p.ageLabel + (p.ageForced ? " \u{1F527}" : "") + (p.daysToNextStage === null ? " \xB7 \u5DF2\u957F\u6210" : "")));
667
+ ui.content.appendChild(age);
668
+ var grid = el("div", "dp-actions");
669
+ for (var i = 0; i < MODES.length; i += 1) {
670
+ (function(key) {
671
+ var info2 = ui.view.actions[key];
672
+ var shelf = ui.view.care[key] ?? [];
673
+ var needsItem = shelf.length > 0;
674
+ var btn = button("dp-btn", { "data-action": key }, function() {
675
+ if (needsItem) {
676
+ ui.picker = ui.picker === key ? null : key;
677
+ ui.renderContent();
678
+ } else {
679
+ ui.send(key);
680
+ }
681
+ });
682
+ btn.setAttribute("data-open-picker", ui.picker === key ? "true" : "false");
683
+ btn.appendChild(el("span", null, CARE_LABEL[key][1]));
684
+ btn.appendChild(el("span", null, CARE_LABEL[key][0]));
685
+ if (needsItem) btn.appendChild(el("span", "dp-count", String(shelf.length)));
686
+ if (!info2.ready || ui.view.dead) {
687
+ btn.disabled = true;
688
+ if (ui.view.dead) btn.appendChild(el("span", "dp-wait", "\u2014"));
689
+ else if (info2.waitSeconds > 0) btn.appendChild(el("span", "dp-wait", info2.waitSeconds + "s"));
690
+ else if (info2.blocked === "away") btn.appendChild(el("span", "dp-wait", "\u4E0D\u5728\u5BB6"));
691
+ }
692
+ grid.appendChild(btn);
693
+ })(MODES[i]);
694
+ }
695
+ ui.content.appendChild(grid);
696
+ if (ui.picker !== null && (ui.view.care[ui.picker] ?? []).length > 0) ui.content.appendChild(pickerPanel(ui, ui.picker));
697
+ ui.content.appendChild(talkRow(ui));
698
+ if (p.memories.length > 0) {
699
+ ui.content.appendChild(el("div", "dp-memo", p.memories.slice(-3).join("\n")));
700
+ }
701
+ }
702
+ function talkRow(ui) {
703
+ var row = el("div", "dp-row dp-talk");
704
+ if (ui.ownerEdit !== null || ui.pigNameEdit !== null) {
705
+ var forPig = ui.pigNameEdit !== null;
706
+ var input = (
707
+ /** @type {HTMLInputElement} */
708
+ el("input", "dp-input")
709
+ );
710
+ input.value = forPig ? ui.pigNameEdit : ui.ownerEdit;
711
+ input.maxLength = 16;
712
+ input.setAttribute(forPig ? "data-pig-input" : "data-owner-input", "true");
713
+ input.addEventListener("input", function() {
714
+ if (forPig) ui.pigNameEdit = input.value;
715
+ else ui.ownerEdit = input.value;
716
+ });
717
+ var save = button("dp-mini", { "data-name-save": forPig ? "pig" : "owner" }, function() {
718
+ var name = ((forPig ? ui.pigNameEdit : ui.ownerEdit) || "").trim();
719
+ if (forPig) ui.pigNameEdit = null;
720
+ else ui.ownerEdit = null;
721
+ if (name !== "") ui.send(forPig ? "name" : "owner", { name });
722
+ ui.renderContent();
723
+ });
724
+ save.textContent = "\u597D";
725
+ var cancel = button("dp-mini dp-mini-plain", { "data-name-cancel": "true" }, function() {
726
+ ui.ownerEdit = null;
727
+ ui.pigNameEdit = null;
728
+ ui.renderContent();
729
+ });
730
+ cancel.textContent = "\u7B97\u4E86";
731
+ row.appendChild(input);
732
+ row.appendChild(save);
733
+ row.appendChild(cancel);
734
+ return row;
735
+ }
736
+ var renameOwner = button("dp-mini dp-mini-plain", { "data-owner-edit": "true" }, function() {
737
+ ui.ownerEdit = ui.view.dialogue.ownerName;
738
+ ui.renderContent();
739
+ });
740
+ renameOwner.textContent = "\u270F\uFE0F \u79F0\u547C";
741
+ renameOwner.title = "\u73B0\u5728\u53EB\u300C" + ui.view.dialogue.ownerName + "\u300D";
742
+ var renamePig = button("dp-mini dp-mini-plain", { "data-pig-edit": "true" }, function() {
743
+ ui.pigNameEdit = ui.view.pig === null ? "" : ui.view.pig.name;
744
+ ui.renderContent();
745
+ });
746
+ renamePig.textContent = "\u270F\uFE0F \u540D\u5B57";
747
+ renamePig.title = ui.view.pig === null ? "\u732A\u8FD8\u6CA1\u6765" : "\u73B0\u5728\u53EB\u300C" + ui.view.pig.name + "\u300D";
748
+ var quiet = button("dp-mini dp-mini-plain", { "data-quiet": ui.view.dialogue.quiet ? "on" : "off" }, function() {
749
+ ui.send("quiet", { on: !ui.view.dialogue.quiet });
750
+ });
751
+ quiet.textContent = ui.view.dialogue.quiet ? "\u{1F515} \u514D\u6253\u6270\u4E2D" : "\u{1F514} \u514D\u6253\u6270";
752
+ row.appendChild(renameOwner);
753
+ row.appendChild(renamePig);
754
+ row.appendChild(quiet);
755
+ return row;
756
+ }
757
+ function renderBanners(ui) {
758
+ if (ui.view.pig !== null && ui.view.dead) {
759
+ var dead = el("div", "dp-alert dp-dead");
760
+ dead.appendChild(el("b", null, "\u{1FAA6} " + ui.view.pig.name + " \u8D70\u4E86" + (ui.view.pig.soul ? "\uFF0C\u7075\u9B42\u8FD8\u7559\u5728\u5893\u7891\u4E0A \u{1F47B}" : "")));
761
+ dead.appendChild(el("div", null, ui.view.pig.soul ? "\u7528\u8FD8\u9B42\u4E39\u53EF\u4EE5\u628A\u5B83\u53EB\u56DE\u6765\uFF0C\u4E5F\u53EF\u4EE5\u9886\u517B\u65B0\u7684" : "\u80CC\u5305\u91CC\u7684\u8FD8\u9B42\u4E39\u5C31\u80FD\u6551\u56DE\u6765"));
762
+ ui.content.appendChild(dead);
763
+ var adoptWrap = el("div", "dp-actions");
764
+ var adopt = button("dp-btn dp-btn-wide", { "data-action": "adopt" }, function() {
765
+ ui.send("adopt");
766
+ });
767
+ adopt.appendChild(el("span", null, "\u{1F4E6}"));
768
+ adopt.appendChild(el("span", null, "\u9886\u517B\u65B0\u732A"));
769
+ adoptWrap.appendChild(adopt);
770
+ ui.content.appendChild(adoptWrap);
771
+ } else if (ui.view.pig !== null && ui.view.pig.illness !== null) {
772
+ var illness = ui.view.pig.illness;
773
+ var sick = el("div", "dp-alert dp-sick");
774
+ sick.appendChild(el("b", null, "\u{1F912} " + illness.name + "\uFF08\u7B2C " + illness.stage + "/4 \u671F\uFF09"));
775
+ sick.appendChild(el("div", null, "\u9700\u8981\u300C" + illness.cureEmoji + illness.cure + "\u300D\u2014\u2014 \u5403\u9519\u836F\u4F1A\u52A0\u91CD"));
776
+ var needed = null;
777
+ var shelf = ui.view.shop || [];
778
+ for (var c = 0; c < shelf.length; c += 1) {
779
+ if (shelf[c].needed) needed = shelf[c];
780
+ }
781
+ for (var t = 0; needed === null && t < shelf.length; t += 1) {
782
+ if (shelf[t].kind === "medicine" && shelf[t].tier === illness.stage) needed = shelf[t];
783
+ }
784
+ for (var n = 0; needed === null && n < shelf.length; n += 1) {
785
+ if (shelf[n].label === illness.cure) needed = shelf[n];
786
+ }
787
+ if (ui.view.canGoOut) {
788
+ sick.appendChild(el("div", "dp-dim", "\u5E26\u75C5\u51FA\u95E8\u62A5\u916C\u51CF\u534A\u3001\u75C5\u60C5\u66F4\u5FEB"));
789
+ }
790
+ if (needed !== null && ui.view.canGoOut && ui.view.pig.coins < needed.price) {
791
+ sick.appendChild(el("div", "dp-dim", "\u8FD8\u5DEE " + needed.price + " \u{1FA99} \u4E70\u300C" + needed.label + "\u300D\uFF0C\u5148\u53BB\u6253\u5DE5"));
792
+ }
793
+ ui.content.appendChild(sick);
794
+ if (illness.doctorFee !== null) {
795
+ var clinic = el("div", "dp-actions");
796
+ var doctor = button("dp-btn dp-btn-wide", { "data-action": "doctor" }, function() {
797
+ ui.send("doctor");
798
+ });
799
+ doctor.appendChild(el("span", null, "\u{1F3E5}"));
800
+ doctor.appendChild(el("span", null, "\u770B\u533B\u751F\uFF08" + illness.doctorFee + " \u{1FA99}\uFF09"));
801
+ clinic.appendChild(doctor);
802
+ ui.content.appendChild(clinic);
803
+ }
804
+ } else if (ui.view.pig !== null && ui.view.activity !== null) {
805
+ var away = el("div", "dp-alert dp-work");
806
+ away.appendChild(el("b", null, ui.view.activity.emoji + " \u5728\u5916\u9762\uFF1A" + ui.view.activity.label));
807
+ away.appendChild(el("div", null, "\u8FD8\u6709 " + ui.view.activity.secondsLeft + " \u79D2"));
808
+ ui.content.appendChild(away);
809
+ var wrap = el("div", "dp-actions");
810
+ var call = button("dp-btn dp-btn-wide", { "data-action": "calloff" }, function() {
811
+ ui.send("calloff");
812
+ });
813
+ call.appendChild(el("span", null, "\u21A9\uFE0F"));
814
+ call.appendChild(el("span", null, "\u53EB\u5B83\u56DE\u6765"));
815
+ wrap.appendChild(call);
816
+ ui.content.appendChild(wrap);
817
+ }
818
+ }
819
+
820
+ // src/client/tabs/study.js
821
+ var INTEREST_TAB = "interest";
822
+ var STAGE_COLOR = { primary: "yellow", middle: "teal", college: "blue", graduate: "purple", beyond: "pink" };
823
+ var INTEREST_COLOR = "orange";
824
+ var FALLBACK_COLORS = ["yellow", "teal", "blue", "purple", "pink", "green", "lime"];
825
+ function standing(sub, stage) {
826
+ if (stage === null || sub.stageKey === "") return "current";
827
+ if (sub.stageKey === stage.key) return "current";
828
+ if (stage.upTo !== null && sub.lessons >= stage.upTo) return "done";
829
+ return "ahead";
830
+ }
831
+ function renderStudyTab(ui) {
832
+ if (ui.view.subjects.length === 0) {
833
+ ui.content.appendChild(el("div", "dp-empty", "\u5BBF\u4E3B\u8FD8\u6CA1\u63D0\u4F9B\u8BFE\u7A0B\u8868\u3002"));
834
+ return;
835
+ }
836
+ var open = ui.drill.study;
837
+ if (open === INTEREST_TAB && ui.view.interests.length > 0) {
838
+ renderInterests(ui);
839
+ return;
840
+ }
841
+ var stage = null;
842
+ for (var d = 0; d < ui.view.stages.length; d += 1) if (ui.view.stages[d].key === open) stage = ui.view.stages[d];
843
+ if (stage === null) {
844
+ renderStages(ui);
845
+ return;
846
+ }
847
+ renderSubjects(ui, stage);
848
+ }
849
+ function renderStages(ui) {
850
+ var stageList = ui.view.stages.length > 0 ? ui.view.stages : STAGES;
851
+ var grid = tileGrid();
852
+ for (var s = 0; s < stageList.length; s += 1) {
853
+ (function(entry, index) {
854
+ var locked = entry.unlocked === false;
855
+ var finished = entry.upTo === null || entry.upTo === void 0 ? 0 : ui.view.subjects.filter(function(sub) {
856
+ return sub.lessons >= entry.upTo;
857
+ }).length;
858
+ grid.appendChild(tile({
859
+ emoji: entry.emoji || "\u{1F4DA}",
860
+ label: entry.label,
861
+ color: STAGE_COLOR[entry.key] ?? FALLBACK_COLORS[index % FALLBACK_COLORS.length],
862
+ locked,
863
+ tag: locked ? "\u{1F512}" : "",
864
+ badge: finished > 0 ? "\u2713" + finished : "",
865
+ data: { "data-stage": entry.key },
866
+ onPick: function() {
867
+ drillTo(ui, "study", entry.key);
868
+ }
869
+ }));
870
+ })(stageList[s], s);
871
+ }
872
+ if (ui.view.interests.length > 0) {
873
+ var certified = ui.view.interests.filter(function(entry) {
874
+ return entry.certified;
875
+ }).length;
876
+ grid.appendChild(tile({
877
+ emoji: "\u{1F3AF}",
878
+ label: "\u5174\u8DA3",
879
+ color: INTEREST_COLOR,
880
+ badge: certified > 0 ? "\u{1F4DC}" + certified : "",
881
+ data: { "data-stage": INTEREST_TAB },
882
+ onPick: function() {
883
+ drillTo(ui, "study", INTEREST_TAB);
884
+ }
885
+ }));
886
+ }
887
+ ui.content.appendChild(grid);
888
+ }
889
+ function renderSubjects(ui, stage) {
890
+ drillHeader(
891
+ ui,
892
+ "study",
893
+ stage.emoji + " " + stage.label,
894
+ stage.minutes + " \u5206\u949F \xB7 " + stage.tuition + " \u{1FA99} \xB7 +" + stage.gain
895
+ );
896
+ var color = STAGE_COLOR[stage.key] ?? FALLBACK_COLORS[Math.max(0, ui.view.stages.indexOf(stage)) % FALLBACK_COLORS.length];
897
+ var grid = tileGrid();
898
+ for (var i = 0; i < ui.view.subjects.length; i += 1) {
899
+ (function(sub) {
900
+ var where = standing(sub, stage);
901
+ var note;
902
+ if (where === "done") note = "\u2713 \u6BD5\u4E1A";
903
+ else if (where === "ahead") note = "\u{1F512} " + (sub.stageLabel || "\u6CA1\u5230");
904
+ else if (stage.upTo !== null) note = sub.lessons - stage.from + "/" + (stage.upTo - stage.from) + " \u8282";
905
+ else note = sub.lessons + " \u8282";
906
+ grid.appendChild(tile({
907
+ emoji: sub.emoji,
908
+ label: sub.label,
909
+ color,
910
+ soft: true,
911
+ note,
912
+ disabled: where !== "current" || !ui.view.canGoOut,
913
+ dim: where === "current" && !sub.affordable,
914
+ data: { "data-subject": sub.key },
915
+ onPick: function() {
916
+ ui.send("study", { subject: sub.key });
917
+ }
918
+ }));
919
+ })(ui.view.subjects[i]);
920
+ }
921
+ ui.content.appendChild(grid);
922
+ }
923
+ function renderInterests(ui) {
924
+ var after = ui.view.interests[0].certificateAfter;
925
+ drillHeader(ui, "study", "\u{1F3AF} \u5174\u8DA3", after > 0 ? "\u4E0A\u6EE1 " + after + " \u6B21\u62FF\u8BC1" : "");
926
+ var grid = tileGrid();
927
+ for (var n = 0; n < ui.view.interests.length; n += 1) {
928
+ (function(entry) {
929
+ var note = entry.certificate === "" ? entry.cost + " \u{1FA99}" : entry.certified ? "\u{1F4DC} \u6709\u8BC1" : "\u{1F4DC} " + entry.times + "/" + entry.certificateAfter;
930
+ grid.appendChild(tile({
931
+ emoji: entry.emoji,
932
+ label: entry.label,
933
+ color: INTEREST_COLOR,
934
+ soft: true,
935
+ note,
936
+ disabled: !ui.view.canGoOut,
937
+ dim: !entry.affordable,
938
+ data: { "data-interest": entry.key },
939
+ onPick: function() {
940
+ ui.send("interest", { interest: entry.key });
941
+ }
942
+ }));
943
+ })(ui.view.interests[n]);
944
+ }
945
+ ui.content.appendChild(grid);
946
+ }
947
+
948
+ // src/client/format.js
949
+ function formatMinutes(minutes) {
950
+ if (minutes < 60) return minutes + " \u5206\u949F";
951
+ const hours = Math.floor(minutes / 60);
952
+ const rest = minutes % 60;
953
+ return rest === 0 ? hours + " \u5C0F\u65F6" : hours + " \u5C0F\u65F6" + rest + " \u5206";
954
+ }
955
+
956
+ // src/client/css-base.js
957
+ var CSS_BASE = [
958
+ // ---------------------------------------------------------------------
959
+ // Animal Crossing design language, transcribed from
960
+ // guokaigdg/animal-island-ui docs/design-system (design-tokens.md and the
961
+ // standalone css-variables.md template).
962
+ //
963
+ // The tokens are declared on the widget root rather than :root: the host
964
+ // page must not inherit them, and they must not be clobbered by it.
965
+ //
966
+ // The rules that shape everything below:
967
+ // · warm earth-brown text on cream parchment, never pure black or grey
968
+ // · 12px minimum radius; buttons and inputs are 50px pills
969
+ // · the thick 3D bottom shadow belongs to primary buttons only
970
+ // · cards carry a border, not an elevation shadow
971
+ // · motion is 0.15-0.35s on cubic-bezier(.4,0,.2,1)
972
+ // · focus rings are yellow or teal, never blue
973
+ // ---------------------------------------------------------------------
974
+ "[data-dsh-pig]{",
975
+ '--ac-font:Nunito,"Noto Sans SC",-apple-system,"PingFang SC","Hiragino Sans GB",sans-serif;',
976
+ "--ac-primary:#19c8b9;--ac-primary-hover:#3dd4c6;--ac-primary-active:#11a89b;",
977
+ "--ac-primary-bg:#e6f9f6;",
978
+ "--ac-text:#794f27;--ac-text-body:#725d42;--ac-text-2:#9f927d;--ac-text-muted:#8a7b66;",
979
+ "--ac-text-disabled:#c4b89e;",
980
+ "--ac-bg:#f8f8f0;--ac-bg-content:rgb(247,243,223);--ac-bg-input:#fffbe7;",
981
+ "--ac-bg-disabled:#f0ece2;",
982
+ "--ac-border:#c4b89e;--ac-border-light:#e5dcc6;--ac-border-hover:#a89878;",
983
+ "--ac-radius-sm:12px;--ac-radius-card:20px;--ac-pill:50px;",
984
+ "--ac-shadow-sm:0 2px 4px 0 rgba(61,52,40,.06);",
985
+ "--ac-shadow:0 3px 10px 0 rgba(61,52,40,.1);",
986
+ "--ac-shadow-lg:0 8px 24px 0 rgba(61,52,40,.16);",
987
+ "--ac-inset:inset 0 2px 4px rgba(114,93,66,.15);",
988
+ // sidebar tokens: the library uses these for the selected menu row, which
989
+ // is exactly the role the icon bar plays here.
990
+ "--ac-active:#b7c6e5;--ac-hover:#d6dff0;",
991
+ "--ac-success:#6fba2c;--ac-warning:#f5c31c;--ac-error:#e05a5a;",
992
+ "--ac-ease:cubic-bezier(.4,0,.2,1);",
993
+ // One place to size the pig; the scene and the panel cap derive from it.
994
+ "--pig-size:56px;--pig-gap-below:12px;--scene-open:132px;--panel-width:292px;",
995
+ "position:fixed;right:18px;bottom:18px;z-index:2147483000;",
996
+ "font-family:var(--ac-font);font-weight:500;letter-spacing:.01em;",
997
+ "-webkit-user-select:none;user-select:none;touch-action:none;",
998
+ // The wrapper spans a column wider and taller than what it paints (the
999
+ // scene's padding, the gap above the panel). Without this it swallows
1000
+ // clicks aimed at the page underneath — which once looked like "sending a
1001
+ // message does nothing" while the whole stack was healthy.
1002
+ "pointer-events:none;",
1003
+ // The pig is the only in-flow child, so the wrapper's box is exactly the
1004
+ // pig's box and the panel can be parked anywhere around it without ever
1005
+ // nudging the pig. `fitPanel` places the panel.
1006
+ "display:block}",
1007
+ "[data-dsh-pig] *{box-sizing:border-box}",
1008
+ "[data-dsh-pig]>*{pointer-events:auto}",
1009
+ // `hidden` MUST win. The UA sheet's `[hidden]{display:none}` ties on
1010
+ // specificity with a single class, so any `.dp-x{display:grid|flex}` rule
1011
+ // below silently beats it and the element keeps rendering. That is exactly
1012
+ // how a collapsed panel ended up showing the icon bar and the hud while
1013
+ // every `el.hidden === true` assertion still passed.
1014
+ "[data-dsh-pig] .dp-card[hidden],[data-dsh-pig] .dp-bar[hidden],",
1015
+ "[data-dsh-pig] .dp-content[hidden],[data-dsh-pig] .dp-hud[hidden],",
1016
+ "[data-dsh-pig] .dp-bubble[hidden],[data-dsh-pig] .dp-scene[hidden],",
1017
+ "[data-dsh-pig] .dp-work[hidden],[data-dsh-pig] .dp-soul[hidden],",
1018
+ "[data-dsh-pig] .dp-poke-hint[hidden],[data-dsh-pig] .dp-daily[hidden],",
1019
+ "[data-dsh-pig] .dp-pig-img[hidden],[data-dsh-pig] .dp-pig-emoji[hidden]{display:none}",
1020
+ /* ---------- the panel: cream parchment, border not shadow ---------- */
1021
+ // Taken out of flow on purpose. In flow it would widen the wrapper, and a
1022
+ // wider wrapper moves the pig — the exact thing this layout exists to
1023
+ // prevent. Absolutely positioned, the wrapper's box stays the pig's box
1024
+ // and `fitPanel` can put the panel on whichever side has room.
1025
+ ".dp-card{position:absolute;right:0;bottom:calc(100% + 8px);width:var(--panel-width);",
1026
+ "border-radius:var(--ac-radius-card);overflow:hidden;",
1027
+ "display:flex;flex-direction:column;",
1028
+ "background:var(--ac-bg);border:2px solid var(--ac-border-light);",
1029
+ // 面板自己钉住基准字号与字体:不钉就会继承宿主页面的 16px,
1030
+ // 详情框那种「没写 font-size 的容器」就会比周围大一倍(用户反馈 #2)。
1031
+ "box-shadow:var(--ac-shadow-lg);color:var(--ac-text-body);font-family:var(--ac-font);font-size:11px}",
1032
+ /* ---------- the pig: never moved, never boxed ---------- */
1033
+ ".dp-scene{position:relative;height:var(--scene-open);background:none;cursor:grab;",
1034
+ "overflow:visible;display:flex;align-items:flex-end;justify-content:flex-end;",
1035
+ "padding:0 6px var(--pig-gap-below);width:max-content}",
1036
+ '.dp-scene[data-dragging="true"]{cursor:grabbing}',
1037
+ // Collapsed the scene is exactly the pig, so the wrapper paints nothing
1038
+ // extra to click through. Open it widens to the panel so the hud and the
1039
+ // speech bubble have somewhere to sit — the pig is right-aligned either
1040
+ // way, so widening costs it no movement.
1041
+ '[data-dsh-pig][data-open="true"] .dp-scene{width:var(--panel-width)}',
1042
+ // Collapsed the scene shrinks to just the pig. An explicit height rather
1043
+ // than `auto` keeps the pig's line box identical in both states, so
1044
+ // opening moves it by exactly zero pixels.
1045
+ '[data-dsh-pig][data-open="false"] .dp-scene{height:calc(var(--pig-size) + var(--pig-gap-below));',
1046
+ "cursor:pointer}",
1047
+ ".dp-pig{line-height:1;transform-origin:50% 85%;cursor:pointer;position:relative;",
1048
+ "filter:drop-shadow(0 4px 6px rgba(61,52,40,.28));animation:dp-bob 1.8s ease-in-out infinite}",
1049
+ // 装扮点位:猪身上固定的几个锚点,每个点位挂一件。
1050
+ // 以后换真立绘时,只改这里的偏移/尺寸,逻辑和存档都不用动。
1051
+ ".dp-dress{position:absolute;inset:0;pointer-events:none;z-index:3}",
1052
+ ".dp-slot{position:absolute;line-height:1;font-size:15px;transform:translate(-50%,-50%)}",
1053
+ '.dp-slot[data-slot="head"]{left:50%;top:2%}',
1054
+ '.dp-slot[data-slot="face"]{left:50%;top:32%}',
1055
+ '.dp-slot[data-slot="neck"]{left:50%;top:60%}',
1056
+ '.dp-slot[data-slot="body"]{left:50%;top:78%;font-size:19px}',
1057
+ '.dp-slot[data-slot="back"]{left:14%;top:42%;font-size:19px}',
1058
+ '.dp-slot[data-slot="feet"]{left:50%;top:99%}',
1059
+ '[data-dsh-pig][data-open="false"] .dp-pig{filter:drop-shadow(0 5px 9px rgba(61,52,40,.26))}',
1060
+ // A petting hand rather than an arrow. Drawn inline as an SVG data URI so
1061
+ // it needs no asset and can carry the palette's warm outline; the hotspot
1062
+ // sits in the palm, which is where a pat actually lands. The `pointer`
1063
+ // after it is the fallback for browsers that refuse a custom cursor.
1064
+ `.dp-pig{cursor:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 30 30"><g fill="%23F7C9B6" stroke="%23794F27" stroke-width="1.7" stroke-linejoin="round"><rect x="10" y="13.5" width="14" height="12" rx="4.8"/><rect x="10.6" y="6.6" width="3.6" height="10" rx="1.8"/><rect x="14.9" y="5.1" width="3.6" height="11.5" rx="1.8"/><rect x="19.2" y="6.6" width="3.6" height="10" rx="1.8"/><rect x="5.7" y="12.4" width="3.4" height="7.8" rx="1.7" transform="rotate(-27 7.4 16.3)"/></g></svg>') 16 24, pointer}`,
1065
+ // Transform-only keyframes: the pig is an ordinary flex item, so there is
1066
+ // no translateX(-50%) centring to preserve.
1067
+ "@keyframes dp-bob{0%,100%{transform:translateY(0) rotate(0deg)}50%{transform:translateY(-7px) rotate(-2.5deg)}}",
1068
+ "@keyframes dp-breathe{0%,100%{transform:translateY(0) scale(1)}50%{transform:translateY(1px) scale(1.09)}}",
1069
+ "@keyframes dp-shake{0%,100%{transform:translateX(0) rotate(0)}20%{transform:translateX(-4px) rotate(-5deg)}60%{transform:translateX(4px) rotate(5deg)}}",
1070
+ "@keyframes dp-spin{0%{transform:rotate(0)}50%{transform:rotate(180deg) scale(1.2)}100%{transform:rotate(360deg)}}",
1071
+ "@keyframes dp-jump{0%{transform:translateY(0)}30%{transform:translateY(-26px) scale(1.12)}60%{transform:translateY(0) scale(.92)}100%{transform:translateY(0)}}",
1072
+ "@keyframes dp-wobble{0%,100%{transform:rotate(0)}20%{transform:rotate(-14deg)}55%{transform:rotate(14deg)}}",
1073
+ "@keyframes dp-cough{0%,100%{transform:translateX(0)}30%{transform:translateX(-4px) rotate(-7deg)}70%{transform:translateX(4px) rotate(6deg)}}",
1074
+ '.dp-pig[data-mood="happy"]{animation-duration:1.15s}',
1075
+ '.dp-pig[data-mood="sleepy"]{animation-name:dp-breathe;animation-duration:3.6s}',
1076
+ '.dp-pig[data-mood="hungry"]{animation-name:dp-shake;animation-duration:2.4s}',
1077
+ '.dp-pig[data-mood="dirty"]{animation-name:dp-breathe;animation-duration:2.6s;filter:sepia(.4) drop-shadow(0 4px 6px rgba(61,52,40,.28))}',
1078
+ '.dp-pig[data-mood="sick"]{animation-name:dp-cough;animation-duration:2.2s;filter:hue-rotate(-28deg) saturate(.75) drop-shadow(0 4px 6px rgba(61,52,40,.28))}',
1079
+ // One pose per activity, so being away reads as a thing the pig is doing.
1080
+ "@keyframes dp-typing{0%,100%{transform:translateY(0) rotate(0)}25%{transform:translateY(-2px) rotate(-1.5deg)}50%{transform:translateY(0) rotate(0)}75%{transform:translateY(-2px) rotate(1.5deg)}}",
1081
+ "@keyframes dp-reading{0%,100%{transform:translateY(0) rotate(0)}35%{transform:translateY(1px) rotate(-5deg)}70%{transform:translateY(1px) rotate(-2deg)}}",
1082
+ "@keyframes dp-walking{0%,100%{transform:translateY(0) rotate(0)}25%{transform:translateY(-6px) rotate(-4deg)}50%{transform:translateY(0) rotate(0)}75%{transform:translateY(-6px) rotate(4deg)}}",
1083
+ '.dp-pig[data-mood="working"]{animation-name:dp-typing;animation-duration:.7s}',
1084
+ '.dp-pig[data-mood="studying"]{animation-name:dp-reading;animation-duration:2.4s}',
1085
+ '.dp-pig[data-mood="traveling"]{animation-name:dp-walking;animation-duration:1s}',
1086
+ '.dp-pig[data-mood="dead"]{animation:none;filter:grayscale(1)}',
1087
+ ".dp-pig[data-react]{animation-duration:.85s;animation-iteration-count:1}",
1088
+ '.dp-pig[data-react="feed"]{animation-name:dp-jump}',
1089
+ '.dp-pig[data-react="bathe"]{animation-name:dp-wobble;animation-duration:1.05s}',
1090
+ '.dp-pig[data-react="play"]{animation-name:dp-spin;animation-duration:.9s}',
1091
+ '.dp-pig[data-react="away"]{animation-name:dp-jump;animation-duration:.9s}',
1092
+ '.dp-pig[data-react="cure"]{animation-name:dp-spin;animation-duration:.9s}',
1093
+ '.dp-pig[data-react="levelup"]{animation-name:dp-jump;animation-duration:.95s}',
1094
+ '.dp-pig[data-react="refuse"]{animation-name:dp-shake;animation-duration:.5s}',
1095
+ /* ---------- what the pig is off doing ---------- */
1096
+ "[data-dsh-pig] .dp-work{display:flex;flex-direction:column;align-items:center;gap:4px;",
1097
+ "margin:0 2px 6px 0}",
1098
+ ".dp-prop{font-size:26px;line-height:1;filter:drop-shadow(0 3px 5px rgba(61,52,40,.22));",
1099
+ "animation:dp-prop-bob 2.4s ease-in-out infinite}",
1100
+ '[data-dsh-pig][data-away="study"] .dp-prop{animation-duration:3.4s}',
1101
+ '[data-dsh-pig][data-away="trip"] .dp-prop{animation-name:dp-prop-swing;animation-duration:1.6s}',
1102
+ '[data-dsh-pig][data-away="interest"] .dp-prop{animation-duration:3.4s}',
1103
+ "@keyframes dp-prop-bob{0%,100%{transform:translateY(0) rotate(-3deg)}50%{transform:translateY(-3px) rotate(3deg)}}",
1104
+ "@keyframes dp-prop-swing{0%,100%{transform:translateY(0) rotate(-8deg)}50%{transform:translateY(-4px) rotate(8deg)}}",
1105
+ ".dp-progress{width:42px;height:7px;border-radius:var(--ac-pill);background:var(--ac-bg-disabled);",
1106
+ "box-shadow:var(--ac-inset);overflow:hidden}",
1107
+ ".dp-progress i{display:block;height:100%;border-radius:var(--ac-pill);",
1108
+ "background:var(--ac-primary);transition:width .5s var(--ac-ease)}",
1109
+ // The scene needs room for the prop; it grows leftward, so the pig stays put.
1110
+ '[data-dsh-pig][data-away="work"] .dp-scene,[data-dsh-pig][data-away="study"] .dp-scene,',
1111
+ '[data-dsh-pig][data-away="interest"] .dp-scene,',
1112
+ '[data-dsh-pig][data-away="trip"] .dp-scene{width:max-content;min-width:132px}',
1113
+ // 加冕后的形态有动作立绘(桌子、书、行李都画在图里):不再摆 emoji 道具,
1114
+ // 动作也收小,免得把画里的东西甩来甩去(立绘与动作来自 PR #2)。
1115
+ '[data-dsh-pig][data-art-actions="true"] .dp-prop{display:none}',
1116
+ '.dp-pig[data-art-actions="true"][data-mood="working"]:not([data-react]){animation:dp-king-work 1.4s ease-in-out infinite}',
1117
+ '.dp-pig[data-art-actions="true"][data-mood="studying"]:not([data-react]){animation:dp-king-study 2.4s ease-in-out infinite}',
1118
+ '.dp-pig[data-art-actions="true"][data-mood="traveling"]:not([data-react]){animation:dp-king-walk .8s ease-in-out infinite}',
1119
+ "@keyframes dp-king-work{0%,100%{transform:translateY(0)}50%{transform:translateY(1px) rotate(1deg)}}",
1120
+ "@keyframes dp-king-study{0%,100%{transform:rotate(-2deg)}50%{transform:rotate(2deg)}}",
1121
+ "@keyframes dp-king-walk{0%,100%{transform:translateY(0) rotate(-2deg)}50%{transform:translateY(-3px) rotate(2deg)}}",
1122
+ /* ---------- hud: a cream tag beside the pig ---------- */
1123
+ ".dp-hud{position:absolute;left:9px;top:7px;display:flex;flex-direction:column;gap:1px;",
1124
+ "font-size:10.5px;font-weight:600;line-height:1.45;color:var(--ac-text);",
1125
+ "background:var(--ac-bg);border:2px solid var(--ac-border-light);padding:5px 10px;",
1126
+ "border-radius:var(--ac-radius-sm);box-shadow:var(--ac-shadow-sm)}",
1127
+ ".dp-hud b{font-weight:700}",
1128
+ // A drawn sprite is sized by the same variable as the emoji, so growing up
1129
+ // works identically either way.
1130
+ ".dp-pig-img{width:var(--pig-size);height:var(--pig-size);display:block;",
1131
+ "-webkit-user-drag:none;user-select:none}",
1132
+ ".dp-pig-emoji{font-size:var(--pig-size);line-height:1}",
1133
+ // No drawings yet — every stage is the same pig, so age reads as size plus
1134
+ // a faded coat on the last one.
1135
+ '[data-dsh-pig][data-faded="true"] .dp-pig-emoji{filter:grayscale(.5) opacity(.72)}',
1136
+ // The box advertises itself: a slow breathing glow plus a label, so it
1137
+ // does not read as scenery.
1138
+ '[data-dsh-pig][data-unhatched="true"] .dp-pig{cursor:pointer;',
1139
+ "animation:dp-box-breathe 2.4s ease-in-out infinite}",
1140
+ '[data-dsh-pig][data-unhatched="true"] .dp-pig-emoji{',
1141
+ "filter:drop-shadow(0 0 0 rgba(255,214,102,0)) drop-shadow(0 4px 6px rgba(61,52,40,.28))}",
1142
+ "@keyframes dp-box-breathe{0%,100%{transform:translateY(0) scale(1)}",
1143
+ "50%{transform:translateY(-3px) scale(1.06)}}",
1144
+ ".dp-poke-hint{position:absolute;right:2px;bottom:-2px;display:flex;align-items:center;gap:3px;",
1145
+ "font-size:9.5px;font-weight:700;color:var(--ac-text);background:var(--ac-bg);",
1146
+ "border:1.5px solid var(--ac-border-light);border-radius:var(--ac-pill);padding:1px 7px;",
1147
+ "box-shadow:0 2px 0 rgba(61,52,40,.12);pointer-events:none;white-space:nowrap;z-index:3;",
1148
+ "animation:dp-hint-bob 1.6s ease-in-out infinite}",
1149
+ "@keyframes dp-hint-bob{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}",
1150
+ // Each poke shakes it harder; the third one opens it instead.
1151
+ '[data-dsh-pig] .dp-pig[data-mood="poke"],',
1152
+ "[data-dsh-pig][data-poke] .dp-pig{animation-name:dp-poke-shake}",
1153
+ '[data-dsh-pig][data-poke="2"] .dp-pig{animation-duration:.28s}',
1154
+ "@keyframes dp-poke-shake{0%,100%{transform:rotate(0)}25%{transform:rotate(-7deg)}",
1155
+ "50%{transform:rotate(6deg)}75%{transform:rotate(-4deg)}}"
1156
+ // The shop's tiles live in css-tiles.js since B8.
1157
+ ].join("");
1158
+
1159
+ // src/client/css-tabs.js
1160
+ var CSS_TABS = [
1161
+ /* ---------- developer tab ---------- */
1162
+ ".dp-dev-note{font-size:10px;color:var(--ac-text-2);margin:4px 0 2px;line-height:1.5}",
1163
+ ".dp-dev-row{display:flex;flex-wrap:wrap;gap:5px;margin:0 0 2px}",
1164
+ ".dp-dev-btn{flex:0 0 auto;font-size:10px;padding:3px 8px}",
1165
+ ".dp-on{background:var(--ac-primary);color:#fff;border-color:var(--ac-primary)}",
1166
+ '[data-dsh-pig][data-dev="true"] .dp-ico[data-tab="dev"]{color:var(--ac-primary)}',
1167
+ /* ---------- the soul that settles on an unclaimed grave ---------- */
1168
+ ".dp-soul{position:absolute;left:50%;transform:translateX(-50%);top:-4px;font-size:22px;",
1169
+ "line-height:1;opacity:.9;pointer-events:none;z-index:1;",
1170
+ "animation:dp-haunt 3.4s ease-in-out infinite}",
1171
+ "@keyframes dp-haunt{0%,100%{transform:translate(-50%,0) scale(1);opacity:.75}",
1172
+ "50%{transform:translate(-50%,-9px) scale(1.08);opacity:1}}",
1173
+ // A grave does not bob about like a living pig.
1174
+ '.dp-pig[data-stage="grave"]{animation:none;filter:grayscale(.35) drop-shadow(0 4px 6px rgba(61,52,40,.3))}',
1175
+ '.dp-pig[data-stage="box"]{animation:dp-box-wobble 3.2s ease-in-out infinite}',
1176
+ "@keyframes dp-box-wobble{0%,100%{transform:rotate(0)}30%{transform:rotate(-4deg)}",
1177
+ "45%{transform:rotate(3deg)}60%{transform:rotate(-2deg)}}",
1178
+ // Patting squashes the pig flat. Short, so rapid clicking keeps up.
1179
+ '[data-dsh-pig] .dp-pig[data-react="pet"]{animation-name:dp-squash;animation-duration:.42s}',
1180
+ "@keyframes dp-squash{0%{transform:scale(1,1)}35%{transform:scale(1.16,.74) translateY(2px)}",
1181
+ "60%{transform:scale(.94,1.08) translateY(-3px)}100%{transform:scale(1,1)}}",
1182
+ /* ---------- speech bubble ---------- */
1183
+ // `z-index` matters: the pig comes later in the DOM, so without it the pig
1184
+ // paints over the bubble whenever the two boxes overlap — which is exactly
1185
+ // what happened when collapsed and the scene was only as wide as the pig.
1186
+ ".dp-bubble{position:absolute;right:8px;top:7px;z-index:2;max-width:162px;padding:6px 10px;",
1187
+ "border-radius:var(--ac-radius-sm);font-size:10.5px;font-weight:600;line-height:1.45;",
1188
+ "color:var(--ac-text-body);background:var(--ac-bg-input);",
1189
+ "border:2px solid var(--ac-border-light);box-shadow:var(--ac-shadow-sm)}",
1190
+ // Tail drawn as a small rotated square so the 2px border stays continuous.
1191
+ '.dp-bubble::after{content:"";position:absolute;left:14px;bottom:-6px;width:8px;height:8px;',
1192
+ "background:var(--ac-bg-input);border-right:2px solid var(--ac-border-light);",
1193
+ "border-bottom:2px solid var(--ac-border-light);transform:rotate(45deg)}",
1194
+ // Reply buttons under a line: small pills, the mint of the primary colour
1195
+ // without the 3D base, which the spec keeps for real primary buttons.
1196
+ ".dp-bubble-replies{display:flex;flex-wrap:wrap;gap:4px;margin-top:5px}",
1197
+ // 猪头上的日常气泡(签到 / 礼包):不用新颜色,沿用主色与卡片底色。
1198
+ // 挂在场景**上方**(不是 top 边缘):折叠时场景就是猪本身,用 top:-6px
1199
+ // 会让气泡叠在猪头上(用户反馈 #6)。
1200
+ ".dp-daily{position:absolute;bottom:calc(100% + 7px);left:50%;width:36px;margin-left:-18px;",
1201
+ "font:inherit;font-size:15px;line-height:1;padding:3px 0;cursor:pointer;text-align:center;",
1202
+ "border:2px solid var(--ac-border);border-radius:50px;background:var(--ac-bg-input);",
1203
+ "box-shadow:0 3px 0 rgba(61,52,40,.14);animation:dp-daily-bob 2.4s var(--ac-ease) infinite}",
1204
+ // 折叠时场景就剩猪本身(而且它还在上下浮动 ±7px),再多让开一点。
1205
+ '[data-dsh-pig][data-open="false"] .dp-daily{bottom:calc(100% + 16px)}',
1206
+ // 展开时场景有面板那么宽、那么高,挂在场景上方会压到图标栏(B8 截图里压在「商店」上):
1207
+ // 改成蹲在猪左边、贴着猪身子(再高会碰到左边的名字框)。
1208
+ '[data-dsh-pig][data-open="true"] .dp-daily{left:auto;margin-left:0;',
1209
+ "right:calc(6px + var(--pig-size) + 10px);bottom:calc(var(--pig-gap-below) + 4px)}",
1210
+ ".dp-daily:hover{border-color:var(--ac-border-hover)}",
1211
+ ".dp-daily:focus-visible{outline:2px solid var(--ac-primary);outline-offset:1px}",
1212
+ // 名字必须独占:叫 dp-bob 会覆盖猪的待机动画(css-base.js),
1213
+ // 而那个动画的 transform 一被替掉,猪就会横跳半个身位。
1214
+ // 只上下浮:横向居中改用 margin,展开时才能挪到猪旁边。
1215
+ "@keyframes dp-daily-bob{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}",
1216
+ // 日记:折叠时只有首句,展开是全文。
1217
+ ".dp-diary{cursor:pointer}",
1218
+ '.dp-diary[data-open="true"] .dp-diary-full{display:block}',
1219
+ ".dp-diary-full{margin-top:4px;line-height:1.5}",
1220
+ ".dp-reply{font:inherit;font-size:10px;font-weight:700;padding:2px 9px;cursor:pointer;",
1221
+ "border-radius:var(--ac-pill);border:2px solid var(--ac-border-light);background:var(--ac-bg);",
1222
+ "color:var(--ac-text);transition:border-color .15s var(--ac-ease)}",
1223
+ ".dp-reply:hover{border-color:var(--ac-border-hover)}",
1224
+ ".dp-reply:focus-visible{outline:2px solid var(--ac-primary);outline-offset:1px}",
1225
+ // Collapsed, the scene is exactly the pig, so a bubble drawn inside it
1226
+ // would sit on the pig's face. Float it above the head with the tail
1227
+ // pointing down, anchored to the right edge so it can never run off the
1228
+ // window. The hearts rise from behind it.
1229
+ '[data-dsh-pig][data-open="false"] .dp-bubble{top:auto;bottom:calc(100% + 8px);',
1230
+ "left:auto;right:0;max-width:230px}",
1231
+ '[data-dsh-pig][data-open="false"] .dp-bubble::after{left:auto;right:26px;',
1232
+ "top:100%;bottom:auto;margin:0;transform:rotate(45deg);",
1233
+ "border:0;border-right:2px solid var(--ac-border-light);",
1234
+ "border-bottom:2px solid var(--ac-border-light)}",
1235
+ /* ---------- icon bar: the library sidebar, laid on its side ---------- */
1236
+ ".dp-bar{display:grid;grid-template-columns:repeat(6,1fr);gap:4px;padding:8px;",
1237
+ "background:var(--ac-bg-content);border-top:2px solid var(--ac-border-light);",
1238
+ "border-bottom:2px solid var(--ac-border-light)}",
1239
+ ".dp-ico{display:flex;flex-direction:column;align-items:center;gap:2px;cursor:pointer;",
1240
+ "font:inherit;font-size:9.5px;font-weight:600;color:var(--ac-text-muted);background:none;",
1241
+ "border:2px solid transparent;border-radius:var(--ac-radius-sm);padding:5px 1px;",
1242
+ "transition:all .2s var(--ac-ease)}",
1243
+ ".dp-ico span.dp-ico-e{font-size:18px;line-height:1}",
1244
+ ".dp-ico:hover{background:var(--ac-hover)}",
1245
+ '.dp-ico[data-active="true"]{background:var(--ac-active);border-color:#9db0d6;',
1246
+ "color:var(--ac-text);font-weight:700}",
1247
+ ".dp-ico:focus-visible{outline:2px solid var(--ac-primary);outline-offset:1px}",
1248
+ "@keyframes dp-pulse{0%,100%{transform:scale(1)}50%{transform:scale(1.18)}}",
1249
+ '.dp-ico[data-alert="true"] span.dp-ico-e{animation:dp-pulse 1.4s ease-in-out infinite}',
1250
+ /* ---------- content ---------- */
1251
+ ".dp-content{padding:12px 13px 13px;overflow-y:auto;flex:1 1 auto;min-height:0}",
1252
+ ".dp-content::-webkit-scrollbar{width:8px}",
1253
+ ".dp-content::-webkit-scrollbar-thumb{background:var(--ac-border-light);border-radius:4px}",
1254
+ ".dp-content::-webkit-scrollbar-track{background:transparent}",
1255
+ ".dp-title{display:flex;justify-content:space-between;align-items:baseline;font-size:11px;",
1256
+ "margin-bottom:8px}",
1257
+ ".dp-title b{font-weight:700;color:var(--ac-text)}",
1258
+ ".dp-title span{color:var(--ac-text-2);font-size:10.5px;font-weight:600}",
1259
+ ".dp-row{display:flex;justify-content:space-between;font-size:11px;font-weight:600;",
1260
+ "color:var(--ac-text-body);margin:2px 0}",
1261
+ ".dp-row b{font-weight:700;color:var(--ac-text)}",
1262
+ /* ---------- attribute bars: pill track with an inset well ---------- */
1263
+ ".dp-meter{height:9px;border-radius:var(--ac-pill);background:var(--ac-bg-disabled);",
1264
+ "box-shadow:var(--ac-inset);overflow:hidden;margin:3px 0 8px}",
1265
+ ".dp-meter i{display:block;height:100%;border-radius:var(--ac-pill);",
1266
+ "background:var(--ac-warning);transition:width .35s var(--ac-ease)}",
1267
+ ".dp-meter.dp-mood i{background:#f8a6b2}",
1268
+ ".dp-meter.dp-clean i{background:#82d5bb}",
1269
+ ".dp-meter.dp-health i{background:#8ac68a}",
1270
+ ".dp-traits{display:flex;gap:10px;font-size:10.5px;font-weight:600;color:var(--ac-text-2);",
1271
+ "margin:8px 0 3px}",
1272
+ /* ---------- banners ---------- */
1273
+ ".dp-alert{margin:0 0 9px;padding:8px 10px;border-radius:var(--ac-radius-sm);",
1274
+ "font-size:10.5px;font-weight:600;line-height:1.55;border:2px solid}",
1275
+ ".dp-alert b{font-weight:700;color:var(--ac-text)}",
1276
+ ".dp-alert.dp-sick{background:#fdeeee;border-color:#f2c2c2}",
1277
+ ".dp-alert.dp-work{background:#eef1fb;border-color:#c3cdf0}",
1278
+ ".dp-alert.dp-dead{background:var(--ac-bg-disabled);border-color:var(--ac-border-light)}",
1279
+ ".dp-alert.dp-legacy{background:#fdf7e2;border-color:#f0dfa8}",
1280
+ /* ---------- buttons: secondary is a cream pill with soft elevation ---- */
1281
+ ".dp-actions{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px}",
1282
+ ".dp-btn{display:flex;align-items:center;justify-content:center;gap:5px;font:inherit;",
1283
+ "font-size:11px;font-weight:700;letter-spacing:.02em;color:var(--ac-text-body);",
1284
+ "cursor:pointer;padding:8px 6px;border-radius:var(--ac-pill);",
1285
+ "border:2px solid var(--ac-border);background:var(--ac-bg-input);",
1286
+ "box-shadow:var(--ac-shadow-sm);transition:all .2s var(--ac-ease)}",
1287
+ ".dp-btn:hover:not(:disabled){transform:translateY(-1px);box-shadow:var(--ac-shadow);",
1288
+ "border-color:var(--ac-border-hover)}",
1289
+ ".dp-btn:active:not(:disabled){transform:translateY(2px);box-shadow:var(--ac-shadow-sm)}",
1290
+ ".dp-btn:focus-visible{outline:2px solid var(--ac-primary);outline-offset:1px}",
1291
+ ".dp-btn:disabled{background:var(--ac-bg-disabled);color:var(--ac-text-disabled);",
1292
+ "border-color:var(--ac-border-light);box-shadow:none;cursor:not-allowed}",
1293
+ ".dp-btn-wide{grid-column:1/-1}",
1294
+ ".dp-btn .dp-wait{color:var(--ac-text-2);font-size:10px;font-weight:600}",
1295
+ /* ---------- segmented control ---------- */
1296
+ ".dp-seg{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin-bottom:9px}",
1297
+ ".dp-seg button{font:inherit;font-size:10.5px;font-weight:600;color:var(--ac-text-muted);",
1298
+ "cursor:pointer;padding:6px 2px;border-radius:var(--ac-pill);",
1299
+ "border:2px solid var(--ac-border-light);background:var(--ac-bg-input);",
1300
+ // One line, always: a label that wraps makes its button taller than the rest.
1301
+ "white-space:nowrap;overflow:hidden;text-overflow:ellipsis;",
1302
+ "transition:all .2s var(--ac-ease)}",
1303
+ ".dp-seg button:hover{background:var(--ac-hover)}",
1304
+ // The work tab has three skills, not four stages.
1305
+ ".dp-seg.dp-seg-3{grid-template-columns:repeat(3,minmax(0,1fr))}",
1306
+ // Work rows: two small buttons on the right, 详情 opens the checklist below.
1307
+ ".dp-job-locked{opacity:.75}",
1308
+ ".dp-job-detail{margin-top:-2px}",
1309
+ ".dp-req{font-size:10.5px;font-weight:600;color:var(--ac-error);line-height:1.6}",
1310
+ ".dp-req.dp-req-ok{color:var(--ac-success)}",
1311
+ '.dp-seg button[data-active="true"]{background:var(--ac-active);border-color:#9db0d6;',
1312
+ "color:var(--ac-text);font-weight:700}",
1313
+ /* ---------- list rows ---------- */
1314
+ // minmax(0,1fr): a long nowrap line must ellipsize, not widen the panel.
1315
+ ".dp-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:7px}",
1316
+ ".dp-list{display:flex;flex-direction:column;gap:7px}",
1317
+ ".dp-shelf{margin:9px 0 1px;font-size:10px;font-weight:700;color:var(--ac-text-2);",
1318
+ "letter-spacing:.04em}",
1319
+ ".dp-shelf:first-child{margin-top:0}",
1320
+ ".dp-item{display:flex;align-items:center;gap:8px;font-size:11px;font-weight:600;",
1321
+ "color:var(--ac-text-body);padding:7px 9px;border-radius:var(--ac-radius-sm);",
1322
+ "background:var(--ac-bg-content);border:2px solid var(--ac-border-light)}",
1323
+ ".dp-item .dp-grow{flex:1;min-width:0}",
1324
+ ".dp-item .dp-dim{color:var(--ac-text-2);font-size:10px;font-weight:500;overflow:hidden;",
1325
+ "text-overflow:ellipsis;white-space:nowrap}",
1326
+ ".dp-item.dp-wanted{background:#fdf7e2;border-color:var(--ac-warning)}",
1327
+ // B6 talk row: name + 改 + 免打扰, and the inline name input.
1328
+ ".dp-talk{gap:6px;margin-top:8px}",
1329
+ ".dp-talk>span{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
1330
+ ".dp-mini-plain{background:var(--ac-bg-input);color:var(--ac-text);border:2px solid var(--ac-border-light);box-shadow:none}",
1331
+ ".dp-mini-plain:hover:not(:disabled){background:var(--ac-hover)}",
1332
+ ".dp-input{flex:1;min-width:0;font:inherit;font-size:11px;padding:3px 8px;border-radius:var(--ac-pill);",
1333
+ "border:2px solid var(--ac-border);background:var(--ac-bg-input);color:var(--ac-text)}",
1334
+ ".dp-input:focus{outline:2px solid var(--ac-primary);outline-offset:1px}",
1335
+ /* ---------- primary buttons: teal pill with the game 3D bottom edge --- */
1336
+ ".dp-mini{font:inherit;font-size:10.5px;font-weight:700;letter-spacing:.02em;color:#fff;",
1337
+ "cursor:pointer;padding:6px 13px;border-radius:var(--ac-pill);",
1338
+ "border:2px solid var(--ac-primary-active);background:var(--ac-primary);",
1339
+ "box-shadow:0 3px 0 0 var(--ac-primary-active);transition:all .15s var(--ac-ease)}",
1340
+ ".dp-mini:hover:not(:disabled){background:var(--ac-primary-hover);transform:translateY(-1px);",
1341
+ "box-shadow:0 4px 0 0 var(--ac-primary-active)}",
1342
+ ".dp-mini:active:not(:disabled){transform:translateY(2px);",
1343
+ "box-shadow:0 1px 0 0 var(--ac-primary-active)}",
1344
+ ".dp-mini:focus-visible{outline:2px solid var(--ac-primary);outline-offset:2px}",
1345
+ ".dp-mini:disabled{background:var(--ac-bg-disabled);color:var(--ac-text-disabled);",
1346
+ "border-color:var(--ac-border-light);box-shadow:none;cursor:not-allowed}",
1347
+ /* ---------- the care item picker ---------- */
1348
+ ".dp-pick{margin-top:9px;padding:9px 10px;border-radius:var(--ac-radius-sm);font-size:10.5px;",
1349
+ "background:var(--ac-bg-content);border:2px solid var(--ac-border-light)}",
1350
+ ".dp-pick-head{font-size:10.5px;font-weight:700;color:var(--ac-text);margin-bottom:7px}",
1351
+ ".dp-cancel{display:block;width:100%;margin-top:8px;font:inherit;font-size:10.5px;",
1352
+ "font-weight:600;color:var(--ac-text-2);cursor:pointer;padding:5px;",
1353
+ "border-radius:var(--ac-pill);border:2px solid var(--ac-border-light);",
1354
+ "background:var(--ac-bg-input);transition:all .2s var(--ac-ease)}",
1355
+ ".dp-cancel:hover{background:var(--ac-hover);color:var(--ac-text)}",
1356
+ ".dp-count{margin-left:2px;font-size:9px;font-weight:700;color:var(--ac-text-2);",
1357
+ "background:var(--ac-bg-content);border-radius:var(--ac-pill);padding:0 5px}",
1358
+ '.dp-btn[data-open-picker="true"]{background:var(--ac-active);border-color:#9db0d6}',
1359
+ '.dp-seg button[data-locked="true"]{color:var(--ac-text-disabled);',
1360
+ "border-style:dashed;background:var(--ac-bg-disabled)}",
1361
+ '.dp-seg button[data-locked="true"]:hover{background:var(--ac-bg-disabled)}',
1362
+ ".dp-locked{margin:0 0 8px;font-size:10.5px;font-weight:600;line-height:1.5;",
1363
+ "color:var(--ac-text-body);background:#fdf7e2;border:2px solid #f0dfa8;",
1364
+ "border-radius:var(--ac-radius-sm);padding:6px 9px}",
1365
+ // The per-job gate reads as a lock, not as another grey stat line: a
1366
+ // threshold the pig cannot see is indistinguishable from a broken button.
1367
+ ".dp-lock{font-size:10px;font-weight:700;line-height:1.5;color:#9a6b1f}",
1368
+ ".dp-empty{color:var(--ac-text-2);font-size:10.5px;font-weight:500;line-height:1.65;",
1369
+ "margin-top:4px}",
1370
+ ".dp-memo{margin-top:9px;padding-top:8px;border-top:2px solid var(--ac-border-light);",
1371
+ "color:var(--ac-text-muted);font-size:10px;font-weight:500;line-height:1.55;",
1372
+ "white-space:pre-wrap;word-break:break-word}",
1373
+ /* ---------- particles and toast ---------- */
1374
+ ".dp-fx{position:absolute;z-index:1;pointer-events:none;font-size:17px;",
1375
+ "animation:dp-rise 1.1s ease-out forwards}",
1376
+ "@keyframes dp-rise{0%{opacity:0;transform:translate(var(--dx0,0),4px) scale(.5)}18%{opacity:1}",
1377
+ "100%{opacity:0;transform:translate(var(--dx,0),-56px) scale(1.15)}}",
1378
+ ".dp-toast{position:absolute;left:9px;right:9px;top:8px;padding:8px 11px;",
1379
+ "border-radius:var(--ac-radius-sm);font-size:10.5px;font-weight:600;line-height:1.5;",
1380
+ "color:var(--ac-text);background:var(--ac-bg-input);border:2px solid var(--ac-border);",
1381
+ "box-shadow:var(--ac-shadow);pointer-events:none;white-space:normal;",
1382
+ "animation:dp-toast 4.6s var(--ac-ease) forwards}",
1383
+ "@keyframes dp-toast{0%{opacity:0;transform:translateY(-8px)}8%{opacity:1;transform:translateY(0)}",
1384
+ "82%{opacity:1}100%{opacity:0;transform:translateY(-6px)}}"
1385
+ ].join("");
1386
+
1387
+ // src/client/css-tiles.js
1388
+ var CSS_TILES = [
1389
+ "[data-dsh-pig]{--tile-pink:#f8a6b2;--tile-purple:#b77dee;--tile-blue:#889df0;",
1390
+ "--tile-yellow:#f7cd67;--tile-orange:#e59266;--tile-teal:#82d5bb;--tile-green:#8ac68a;",
1391
+ "--tile-red:#fc736d;--tile-lime:#d1da49;--tile-peach:#e18c6f;--tile-brown:#9a835a}",
1392
+ '.dp-tile[data-color="pink"]{--tile-c:var(--tile-pink)}',
1393
+ '.dp-tile[data-color="purple"]{--tile-c:var(--tile-purple)}',
1394
+ '.dp-tile[data-color="blue"]{--tile-c:var(--tile-blue)}',
1395
+ '.dp-tile[data-color="yellow"]{--tile-c:var(--tile-yellow)}',
1396
+ '.dp-tile[data-color="orange"]{--tile-c:var(--tile-orange)}',
1397
+ '.dp-tile[data-color="teal"]{--tile-c:var(--tile-teal)}',
1398
+ '.dp-tile[data-color="green"]{--tile-c:var(--tile-green)}',
1399
+ '.dp-tile[data-color="red"]{--tile-c:var(--tile-red)}',
1400
+ '.dp-tile[data-color="lime"]{--tile-c:var(--tile-lime)}',
1401
+ '.dp-tile[data-color="peach"]{--tile-c:var(--tile-peach)}',
1402
+ '.dp-tile[data-color="brown"]{--tile-c:var(--tile-brown)}',
1403
+ // The grid: three columns that can never be widened by their content.
1404
+ ".dp-tiles{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px 8px;padding:4px 2px 2px}",
1405
+ // A tile is a column: the coloured square, then its name, then a note.
1406
+ ".dp-tile{font:inherit;display:flex;flex-direction:column;align-items:center;gap:4px;min-width:0;",
1407
+ "padding:0;margin:0;border:0;background:none;cursor:pointer;color:var(--ac-text)}",
1408
+ ".dp-tile-icon{position:relative;display:flex;align-items:center;justify-content:center;",
1409
+ "width:50px;height:50px;border-radius:15px;background:var(--tile-c,var(--ac-bg-content));",
1410
+ "box-shadow:0 3px 0 rgba(61,52,40,.16);transition:transform .15s var(--ac-ease),box-shadow .15s var(--ac-ease)}",
1411
+ ".dp-tile-e{font-size:24px;line-height:1;filter:drop-shadow(0 1px 1px rgba(61,52,40,.18))}",
1412
+ ".dp-tile:hover:not(:disabled) .dp-tile-icon{transform:translateY(-2px);box-shadow:0 5px 0 rgba(61,52,40,.16)}",
1413
+ ".dp-tile:active:not(:disabled) .dp-tile-icon{transform:translateY(2px);box-shadow:0 1px 0 rgba(61,52,40,.16)}",
1414
+ ".dp-tile:focus-visible{outline:none}",
1415
+ ".dp-tile:focus-visible .dp-tile-icon{outline:2px solid var(--ac-primary);outline-offset:2px}",
1416
+ // One line each, never wrapping: every tile in a row stays the same height.
1417
+ ".dp-tile-n,.dp-tile-note{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.25}",
1418
+ ".dp-tile-n{font-size:10.5px;font-weight:700}",
1419
+ ".dp-tile-note{font-size:9.5px;font-weight:600;color:var(--ac-text-2);margin-top:-2px}",
1420
+ // Corner marks on the square: a count top-right, a word top-left.
1421
+ ".dp-tile-badge,.dp-tile-tag{position:absolute;top:-5px;font-size:9px;font-weight:800;line-height:1;",
1422
+ "padding:3px 5px;border-radius:var(--ac-pill);white-space:nowrap;border:2px solid var(--ac-bg)}",
1423
+ ".dp-tile-badge{right:-6px;background:var(--ac-primary);color:#fff}",
1424
+ ".dp-tile-tag{left:-6px;background:var(--ac-warning);color:var(--ac-text)}",
1425
+ // Second layer: the same colour, a shade paler and a little smaller.
1426
+ // Sizes trimmed on 2026-10-01 (owner: the tiles were too big): 50px / 44px.
1427
+ ".dp-tile-soft .dp-tile-icon{width:44px;height:44px;border-radius:13px;",
1428
+ "background:color-mix(in srgb,var(--tile-c) 42%,#fffbe7)}",
1429
+ ".dp-tile-soft .dp-tile-e{font-size:21px}",
1430
+ // Locked: greyed but still openable (a stage can be looked into before it opens).
1431
+ '.dp-tile[data-locked="true"] .dp-tile-icon{filter:grayscale(.75);opacity:.6}',
1432
+ '.dp-tile[data-dim="true"] .dp-tile-icon,.dp-tile:disabled .dp-tile-icon{opacity:.45;box-shadow:none}',
1433
+ '.dp-tile[data-dim="true"] .dp-tile-n,.dp-tile:disabled .dp-tile-n{color:var(--ac-text-2)}',
1434
+ ".dp-tile:disabled{cursor:default}",
1435
+ '.dp-tile[data-active="true"] .dp-tile-icon{outline:3px solid var(--ac-active);outline-offset:2px}',
1436
+ // The second layer's top row: back, title, one grey line.
1437
+ ".dp-drill{display:flex;align-items:center;gap:7px;margin:0 0 10px}",
1438
+ ".dp-drill-back{font:inherit;font-size:16px;font-weight:800;line-height:1;width:26px;height:26px;",
1439
+ "flex:none;cursor:pointer;color:var(--ac-text);border-radius:50%;",
1440
+ "border:2px solid var(--ac-border-light);background:var(--ac-bg-input)}",
1441
+ ".dp-drill-back:hover{border-color:var(--ac-border-hover)}",
1442
+ ".dp-drill-title{font-size:12px;font-weight:800;color:var(--ac-text);white-space:nowrap}",
1443
+ ".dp-drill-info{flex:1;min-width:0;text-align:right;font-size:10px;font-weight:600;",
1444
+ "color:var(--ac-text-2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
1445
+ // B9: the home screen replaces the bottom icon bar. The bar still exists
1446
+ // (its icons carry the alert state the home tiles read) but is not shown.
1447
+ "[data-dsh-pig] .dp-card .dp-bar{display:none}",
1448
+ ".dp-app-head{margin-bottom:12px}",
1449
+ // Out working, the collapsed scene shrinks to pig + prop; open, it must stay
1450
+ // as wide as the panel, or the name plate is squeezed onto the pig.
1451
+ '[data-dsh-pig][data-open="true"][data-away] .dp-scene{width:var(--panel-width)}',
1452
+ // Banners only live on the status tab now, with room to breathe below.
1453
+ "[data-dsh-pig] .dp-alert{margin-bottom:14px}",
1454
+ "[data-dsh-pig] .dp-alert + .dp-actions{margin-bottom:14px}",
1455
+ ".dp-job-go{display:block;width:100%;margin-top:9px}",
1456
+ // A picked tile's details (a diary page, a souvenir's story) sit under the grid.
1457
+ ".dp-tile-card{margin-top:12px}",
1458
+ // 更新 App: the release notes keep their line breaks but stay short.
1459
+ ".dp-update-notes{white-space:pre-wrap;font-size:10.5px;line-height:1.5;color:var(--ac-text-2);max-height:120px;overflow:auto;margin:4px 0 6px}",
1460
+ ".dp-update-back{margin-top:10px;width:100%}",
1461
+ ".dp-update-now{margin-bottom:12px}",
1462
+ ".dp-update-now .dp-btn,.dp-update-detail .dp-btn{width:100%;margin-top:8px}"
1463
+ ].join("");
1464
+
1465
+ // src/client/css-card.js
1466
+ var CSS_CARD = [
1467
+ ".dp-vcard{position:relative;padding:14px 14px 12px;border-radius:20px;color:var(--ac-text-body);",
1468
+ "--vc-dot:rgba(196,184,158,.15);--vc-dot2:rgba(196,184,158,.1);--vc-bg:rgb(247,243,223);--vc-line:#d4c4a8;",
1469
+ "background:radial-gradient(circle,var(--vc-dot) 1.5px,transparent 1.5px),",
1470
+ "radial-gradient(circle,var(--vc-dot2) 1px,transparent 1px),var(--vc-bg);",
1471
+ "background-size:28px 28px,14px 14px;background-position:0 0,7px 7px;border:1.5px solid var(--vc-line)}",
1472
+ '.dp-vcard[data-sex="girl"]{--vc-dot:rgba(248,166,178,.18);--vc-dot2:rgba(255,200,210,.12);--vc-bg:#fde4e8;--vc-line:#f8a6b2}',
1473
+ '.dp-vcard[data-sex="boy"]{--vc-dot:rgba(136,157,240,.18);--vc-dot2:rgba(180,195,255,.12);--vc-bg:#e8edff;--vc-line:#889df0}',
1474
+ // Top: the photo and who it is.
1475
+ ".dp-vcard-top{display:flex;align-items:center;gap:12px;margin-bottom:12px}",
1476
+ ".dp-vcard-avatar{flex:none;width:72px;height:72px;border-radius:18px;display:flex;align-items:center;",
1477
+ "justify-content:center;background:#fffbe7;border:2px solid var(--vc-line);box-shadow:0 3px 0 rgba(61,52,40,.12)}",
1478
+ ".dp-vcard-img{width:56px;height:56px;display:block}",
1479
+ ".dp-vcard-e{font-size:40px;line-height:1}",
1480
+ ".dp-vcard-who{display:flex;flex-direction:column;gap:3px;min-width:0}",
1481
+ ".dp-vcard-name{font-size:15px;font-weight:800;color:var(--ac-text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
1482
+ ".dp-vcard-sub{font-size:10.5px;font-weight:600;color:var(--ac-text-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
1483
+ // 「标签:值」 rows.
1484
+ ".dp-vcard-row{display:flex;align-items:center;gap:6px;margin-top:7px;min-width:0}",
1485
+ ".dp-vcard-label{flex:none;width:44px;font-size:10.5px;font-weight:700;color:var(--ac-text)}",
1486
+ ".dp-vcard-value{flex:1;min-width:0;padding:5px 11px;border-radius:var(--ac-pill);background:#faf8f2;",
1487
+ "font-size:11px;font-weight:600;color:var(--ac-text-body);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
1488
+ // The motto is the pig talking: a bubble that may take two lines.
1489
+ ".dp-vcard-motto-row{align-items:flex-start}",
1490
+ ".dp-vcard-motto-row .dp-vcard-label{margin-top:6px}",
1491
+ ".dp-vcard-value.dp-vcard-motto{border-radius:12px;white-space:normal;line-height:1.45}",
1492
+ ".dp-vcard-edit{flex:none;font:inherit;font-size:12px;line-height:1;width:24px;height:24px;padding:0;cursor:pointer;",
1493
+ "border-radius:50%;border:2px solid var(--vc-line);background:#fffbe7}",
1494
+ ".dp-vcard-edit:hover{background:var(--ac-hover)}",
1495
+ ".dp-vcard-input{flex:1;min-width:0;padding:3px 9px;font-size:11px}",
1496
+ // In-place editing: the two buttons stay as small as the pencil they replace.
1497
+ ".dp-vcard-row .dp-mini{flex:none;padding:3px 9px;font-size:10px;box-shadow:none}",
1498
+ "[data-dsh-pig] .dp-vcard-row .dp-mini.dp-mini-plain{background:#fffbe7;color:var(--ac-text);border:2px solid var(--vc-line);box-shadow:none}",
1499
+ ".dp-vcard-foot{margin-top:12px;padding-top:9px;border-top:1.5px dashed var(--vc-line);",
1500
+ "font-size:10px;font-weight:600;color:var(--ac-text-2);text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
1501
+ // 加冕 App: one cream box per form, its picture on the left, conditions as small chips.
1502
+ ".dp-crown{margin-bottom:10px;padding:10px 12px;border-radius:16px;background:#fffbe7;border:2px dashed #e8c66a}",
1503
+ ".dp-crown.dp-crown-now{border-style:solid;background:#fdf3d0}",
1504
+ ".dp-crown-top{display:flex;gap:10px;align-items:flex-start}",
1505
+ ".dp-crown-pic{flex:none;width:58px;height:58px;border-radius:14px;display:flex;align-items:center;justify-content:center;",
1506
+ "background:#fff;border:2px solid #f0dca0;font-size:30px}",
1507
+ ".dp-crown-img{width:50px;height:50px;display:block}",
1508
+ ".dp-crown-side{flex:1;min-width:0}",
1509
+ ".dp-crown-head{font-size:12px;font-weight:800;color:var(--ac-text);margin-bottom:6px}",
1510
+ ".dp-crown-done{font-size:11px;font-weight:700;color:#3f8a62}",
1511
+ ".dp-crown-reqs{display:flex;flex-wrap:wrap;gap:4px}",
1512
+ ".dp-crown-req{padding:2px 7px;border-radius:var(--ac-pill);font-size:10px;font-weight:700;white-space:nowrap;",
1513
+ "background:#f3ece0;color:var(--ac-text-2)}",
1514
+ ".dp-crown-req.dp-crown-ok{background:#dff3e8;color:#3f8a62}",
1515
+ ".dp-crown .dp-btn{width:100%;margin-top:9px}"
1516
+ ].join("");
1517
+
1518
+ // src/client/styles.js
1519
+ var CSS = CSS_BASE + CSS_TABS + CSS_TILES + CSS_CARD;
1520
+
1521
+ // src/client/tabs/travel.js
1522
+ function renderTravelTab(ui) {
1523
+ if (ui.view.trips.length === 0) {
1524
+ ui.content.appendChild(el("div", "dp-empty", "\u5BBF\u4E3B\u8FD8\u6CA1\u63D0\u4F9B\u76EE\u7684\u5730\u3002"));
1525
+ return;
1526
+ }
1527
+ var list = el("div", "dp-list");
1528
+ for (var i = 0; i < ui.view.trips.length; i += 1) {
1529
+ (function(trip) {
1530
+ var row = el("div", "dp-item");
1531
+ row.appendChild(el("span", null, trip.emoji));
1532
+ var grow = el("div", "dp-grow");
1533
+ grow.appendChild(el("div", null, trip.label));
1534
+ grow.appendChild(el("div", "dp-dim", formatMinutes(trip.minutes) + " \xB7 " + trip.cost + " \u{1FA99}" + (trip.bestRarity ? " \xB7 \u53EF\u5E26\u56DE " + trip.bestRarityEmoji + trip.bestRarity : "")));
1535
+ row.appendChild(grow);
1536
+ var go = button("dp-mini", { "data-trip": trip.key }, function() {
1537
+ ui.send("trip", { trip: trip.key });
1538
+ });
1539
+ go.textContent = "\u51FA\u53D1";
1540
+ go.disabled = !ui.view.canGoOut || !trip.affordable;
1541
+ row.appendChild(go);
1542
+ list.appendChild(row);
1543
+ })(ui.view.trips[i]);
1544
+ }
1545
+ ui.content.appendChild(list);
1546
+ var souvenirs = ui.view.pig.souvenirs;
1547
+ var head = el("div", "dp-title");
1548
+ head.style.marginTop = "10px";
1549
+ head.appendChild(el("b", null, "\u{1F381} \u7EAA\u5FF5\u54C1 " + souvenirs.length));
1550
+ ui.content.appendChild(head);
1551
+ if (souvenirs.length === 0) {
1552
+ ui.content.appendChild(el("div", "dp-empty", "\u8FD8\u6CA1\u51FA\u8FC7\u8FDC\u95E8\u3002"));
1553
+ return;
1554
+ }
1555
+ var chips = el("div", "dp-grid");
1556
+ for (var s = 0; s < souvenirs.length; s += 1) {
1557
+ (function(entry) {
1558
+ var chip = button("dp-item", { "data-souvenir": entry.key }, function() {
1559
+ ui.souvenirPick = ui.souvenirPick === entry.key ? null : entry.key;
1560
+ ui.renderContent();
1561
+ });
1562
+ chip.appendChild(el("span", null, entry.emoji));
1563
+ var grow = el("div", "dp-grow");
1564
+ grow.appendChild(el("div", null, entry.label));
1565
+ grow.appendChild(el("div", "dp-dim", entry.rarityEmoji + entry.rarityLabel + (entry.price > 0 ? " \xB7 \u503C " + entry.price + " \u{1FA99}" : "")));
1566
+ chip.appendChild(grow);
1567
+ chips.appendChild(chip);
1568
+ })(souvenirs[s]);
1569
+ }
1570
+ ui.content.appendChild(chips);
1571
+ var picked = null;
1572
+ for (var q = 0; q < souvenirs.length; q += 1) if (souvenirs[q].key === ui.souvenirPick) picked = souvenirs[q];
1573
+ if (picked !== null) {
1574
+ var souvenirCard = el("div", "dp-locked");
1575
+ souvenirCard.appendChild(el("div", null, picked.emoji + " " + picked.label + " \xB7 " + picked.rarityEmoji + picked.rarityLabel + (picked.fromLabel === "" ? "" : " \xB7 \u6765\u81EA" + picked.fromLabel)));
1576
+ souvenirCard.appendChild(el("div", null, picked.story === "" ? "\uFF08\u8FD9\u53EA\u7EAA\u5FF5\u54C1\u662F\u65E7\u7248\u672C\u5E26\u56DE\u6765\u7684\uFF0C\u6CA1\u6709\u7559\u4E0B\u6545\u4E8B\u3002\uFF09" : "\u300C" + picked.story + "\u300D"));
1577
+ if (picked.price > 0) {
1578
+ var sell = button("dp-mini", { "data-sell": picked.key }, function() {
1579
+ ui.souvenirPick = null;
1580
+ ui.send("sell", { souvenir: picked.key });
1581
+ });
1582
+ sell.textContent = "\u5356\u6389 +" + picked.price + " \u{1FA99}";
1583
+ sell.style.marginTop = "6px";
1584
+ souvenirCard.appendChild(sell);
1585
+ }
1586
+ ui.content.appendChild(souvenirCard);
1587
+ }
1588
+ }
1589
+
1590
+ // src/client/tabs/work.js
1591
+ var SKILLS = [
1592
+ { key: "strong", emoji: "\u{1F4AA}", label: "\u6B66\u529B", color: "orange" },
1593
+ { key: "charm", emoji: "\u2728", label: "\u9B45\u529B", color: "pink" },
1594
+ { key: "intel", emoji: "\u{1F9E0}", label: "\u667A\u529B", color: "blue" }
1595
+ ];
1596
+ function renderWorkTab(ui) {
1597
+ if (ui.view.jobs.length === 0) {
1598
+ ui.content.appendChild(el("div", "dp-empty", "\u5BBF\u4E3B\u8FD8\u6CA1\u63D0\u4F9B\u5DE5\u4F5C\u5217\u8868\u3002"));
1599
+ return;
1600
+ }
1601
+ var bySkill = ui.view.jobs.some(function(job) {
1602
+ return job.trait !== "";
1603
+ });
1604
+ if (!bySkill) {
1605
+ renderJobs(ui, ui.view.jobs, "orange");
1606
+ return;
1607
+ }
1608
+ var skill = SKILLS.find(function(entry) {
1609
+ return entry.key === ui.drill.work;
1610
+ });
1611
+ if (skill === void 0) {
1612
+ renderSkills(ui);
1613
+ return;
1614
+ }
1615
+ var chosen = skill;
1616
+ drillHeader(ui, "work", chosen.emoji + " " + chosen.label, "");
1617
+ renderJobs(ui, ui.view.jobs.filter(function(job) {
1618
+ return job.trait === chosen.key;
1619
+ }), chosen.color);
1620
+ }
1621
+ function renderSkills(ui) {
1622
+ var grid = tileGrid();
1623
+ for (var s = 0; s < SKILLS.length; s += 1) {
1624
+ (function(skill) {
1625
+ var open = ui.view.jobs.filter(function(job) {
1626
+ return job.trait === skill.key && job.qualified;
1627
+ }).length;
1628
+ grid.appendChild(tile({
1629
+ emoji: skill.emoji,
1630
+ label: skill.label,
1631
+ color: skill.color,
1632
+ badge: open > 0 ? String(open) : "",
1633
+ data: { "data-skill": skill.key },
1634
+ onPick: function() {
1635
+ drillTo(ui, "work", skill.key);
1636
+ }
1637
+ }));
1638
+ })(SKILLS[s]);
1639
+ }
1640
+ ui.content.appendChild(grid);
1641
+ }
1642
+ function renderJobs(ui, jobs, color) {
1643
+ var grid = tileGrid();
1644
+ var picked = null;
1645
+ for (var i = 0; i < jobs.length; i += 1) {
1646
+ (function(job) {
1647
+ var active = ui.drill.pick === job.key;
1648
+ if (active) picked = job;
1649
+ var locked = job.qualified === false;
1650
+ grid.appendChild(tile({
1651
+ emoji: job.emoji,
1652
+ label: job.label,
1653
+ color,
1654
+ soft: true,
1655
+ active,
1656
+ note: job.minutes + "\u5206\xB7" + job.coins + "\u{1FA99}",
1657
+ tag: locked ? "\u{1F512}" : "",
1658
+ dim: locked,
1659
+ data: { "data-job-tile": job.key },
1660
+ onPick: function() {
1661
+ ui.drill.pick = active ? null : job.key;
1662
+ ui.renderContent();
1663
+ }
1664
+ }));
1665
+ })(jobs[i]);
1666
+ }
1667
+ ui.content.appendChild(grid);
1668
+ if (picked !== null) ui.content.appendChild(jobDetails(ui, picked));
1669
+ }
1670
+ function jobDetails(ui, job) {
1671
+ var box = el("div", "dp-pick dp-tile-card dp-job-detail");
1672
+ box.appendChild(el("div", "dp-pick-head", job.emoji + " " + job.label + " \xB7 " + (job.qualified ? "\u6761\u4EF6\u90FD\u591F\u4E86" : "\u8FD8\u5DEE\u8FD9\u4E9B")));
1673
+ for (var r = 0; r < job.requirements.length; r += 1) {
1674
+ var need = job.requirements[r];
1675
+ var have = need.kind === "level" ? "\uFF08\u73B0\u5728 Lv." + need.have + "\uFF09" : need.kind === "certificate" ? "\uFF08" + need.have + "/" + need.need + " \u6B21\uFF09" : need.kind === "every" || need.kind === "anyOf" ? "\uFF08" + need.have + "/" + need.need + " \u95E8\uFF09" : "\uFF08\u73B0\u5728 " + need.have + " \u8282\uFF09";
1676
+ box.appendChild(el("div", need.met ? "dp-req dp-req-ok" : "dp-req", (need.met ? "\u2713 " : "\u2717 ") + need.text + (need.met ? "" : " " + have)));
1677
+ }
1678
+ if (job.requirements.length === 0 && job.lockText) box.appendChild(el("div", "dp-req", "\u2717 " + job.lockText));
1679
+ box.appendChild(el("div", "dp-dim", job.minutes + " \u5206\u949F \xB7 " + job.coins + " \u{1FA99} \xB7 " + job.traitEmoji + job.traitLabel + " " + job.traitPoints + (job.payPercent > 0 ? "\uFF08+" + job.payPercent + "%\uFF09" : "") + " \xB7 \u9971\u98DF " + job.satiety + " \xB7 \u6E05\u6D01 " + job.cleanliness));
1680
+ var go = button("dp-btn dp-btn-wide dp-job-go", { "data-job": job.key }, function() {
1681
+ ui.send("work", { job: job.key });
1682
+ });
1683
+ go.textContent = "\u{1F4BC} \u51FA\u53D1";
1684
+ go.disabled = !ui.view.canGoOut || job.qualified === false;
1685
+ box.appendChild(go);
1686
+ return box;
1687
+ }
1688
+
1689
+ // src/client/art.js
1690
+ var REACTION_ART = { feed: "eat", bathe: "bathe", play: "play", pet: "pet", cure: "relaxed", levelup: "relaxed" };
1691
+ var ACTIVITY_ART = { work: "work", study: "study", interest: "study", trip: "trip" };
1692
+ function syncPigArt(pig, image) {
1693
+ var base = pig.getAttribute("data-art");
1694
+ if (!base) return;
1695
+ var art = base;
1696
+ if (pig.getAttribute("data-art-actions") === "true") {
1697
+ var action = REACTION_ART[pig.getAttribute("data-react")] || ACTIVITY_ART[pig.getAttribute("data-activity")];
1698
+ if (action) art += "-" + action;
1699
+ }
1700
+ var src = ART_URL + art + ".svg";
1701
+ if (image.getAttribute("src") !== src) image.src = src;
1702
+ }
1703
+
1704
+ // src/client/effects.js
1705
+ function createEffects(deps) {
1706
+ var scene = deps.scene;
1707
+ var pig = deps.pig;
1708
+ var card = deps.card;
1709
+ var bubble = deps.bubble;
1710
+ var isStopped = deps.isStopped;
1711
+ var reactTimer = null;
1712
+ var bubbleTimer = null;
1713
+ function react(kind, ms) {
1714
+ if (reactTimer !== null) window.clearTimeout(reactTimer);
1715
+ pig.removeAttribute("data-react");
1716
+ void pig.offsetWidth;
1717
+ pig.setAttribute("data-react", kind);
1718
+ syncPigArt(pig, deps.pigArt);
1719
+ reactTimer = window.setTimeout(function() {
1720
+ pig.removeAttribute("data-react");
1721
+ syncPigArt(pig, deps.pigArt);
1722
+ reactTimer = null;
1723
+ }, ms || 900);
1724
+ }
1725
+ function burst(emojis, count) {
1726
+ for (var i = 0; i < (count || 1); i += 1) {
1727
+ (function(index) {
1728
+ window.setTimeout(function() {
1729
+ if (isStopped()) return;
1730
+ var node = el("span", "dp-fx", emojis[index % emojis.length]);
1731
+ node.style.setProperty("--dx", Math.round((Math.random() - 0.5) * 46) + "px");
1732
+ var spot = headSpot();
1733
+ node.style.left = spot.x + Math.round((Math.random() - 0.5) * 22) + "px";
1734
+ node.style.top = spot.y + "px";
1735
+ scene.appendChild(node);
1736
+ window.setTimeout(function() {
1737
+ node.remove();
1738
+ }, 1200);
1739
+ }, index * 110);
1740
+ })(i);
1741
+ }
1742
+ }
1743
+ function headSpot() {
1744
+ var fallback = { x: 24, y: 8 };
1745
+ if (typeof pig.getBoundingClientRect !== "function" || typeof scene.getBoundingClientRect !== "function") return fallback;
1746
+ var p = pig.getBoundingClientRect();
1747
+ var s = scene.getBoundingClientRect();
1748
+ if (p.width === 0 && p.height === 0) return fallback;
1749
+ return { x: p.left - s.left + p.width / 2, y: p.top - s.top - 20 };
1750
+ }
1751
+ var REACTIONS = {
1752
+ hatch: { kind: "levelup", ms: 980, fx: ["\u{1F95A}", "\u2728", "\u{1F416}", "\u{1F389}"], count: 4, say: "\u5B75\u51FA\u6765\u5566\uFF01" },
1753
+ feed: { kind: "feed", ms: 900, fx: ["\u{1F34E}", "\u{1F60B}", "\u2728"], count: 3, say: "\u5403\u6389\u4E86\uFF01" },
1754
+ bathe: { kind: "bathe", ms: 1050, fx: ["\u{1FAE7}", "\u{1FAE7}", "\u{1F4A7}", "\u2728"], count: 4, say: "\u6D17\u5E72\u51C0\u5566\uFF5E" },
1755
+ play: { kind: "play", ms: 900, fx: ["\u{1F3BE}", "\u2B50", "\u{1F4A8}"], count: 3, say: "\u597D\u5F00\u5FC3\uFF01" },
1756
+ pet: { kind: "pet", ms: 420, fx: ["\u2764\uFE0F"], count: 1, say: "\u597D\u8212\u670D\u2026" },
1757
+ work: { kind: "away", ms: 900, fx: ["\u{1F4BC}", "\u{1F9F1}", "\u{1FA99}"], count: 3, say: "\u51FA\u95E8\u6253\u5DE5\uFF01" },
1758
+ study: { kind: "away", ms: 900, fx: ["\u{1F4DA}", "\u270F\uFE0F", "\u{1F9E0}"], count: 3, say: "\u4E0A\u5B66\u53BB\uFF01" },
1759
+ trip: { kind: "away", ms: 900, fx: ["\u{1F9F3}", "\u{1F5FA}", "\u2728"], count: 3, say: "\u51FA\u53D1\u65C5\u884C\uFF01" },
1760
+ calloff: { kind: "refuse", ms: 520, fx: ["\u{1F4A8}"], count: 1, say: "\u63D0\u524D\u56DE\u6765\u4E86\u2026" },
1761
+ buy: { kind: "pet", ms: 620, fx: ["\u{1FA99}", "\u{1F6D2}"], count: 2, say: "\u4E70\u5230\u4E86\uFF01" },
1762
+ use: { kind: "pet", ms: 620, fx: ["\u2728"], count: 2, say: "\u7528\u6389\u4E86\u3002" }
1763
+ };
1764
+ function flash(action) {
1765
+ var spec = REACTIONS[action];
1766
+ if (spec === void 0) return;
1767
+ react(spec.kind, spec.ms);
1768
+ burst(spec.fx, spec.count);
1769
+ var lines = action === "pet" ? PET_LINES : null;
1770
+ showBubble(lines === null ? spec.say : lines[Math.floor(Math.random() * lines.length)], 1600);
1771
+ }
1772
+ var bubbleTimer = null;
1773
+ function showBubble(text, ms) {
1774
+ if (bubbleTimer !== null) window.clearTimeout(bubbleTimer);
1775
+ bubble.textContent = text;
1776
+ bubble.hidden = false;
1777
+ bubbleTimer = window.setTimeout(function() {
1778
+ bubble.hidden = true;
1779
+ bubbleTimer = null;
1780
+ }, ms || 2600);
1781
+ }
1782
+ function showLine(text, replies, onReply) {
1783
+ if (replies.length === 0) {
1784
+ showBubble(text, 2600);
1785
+ return;
1786
+ }
1787
+ if (bubbleTimer !== null) window.clearTimeout(bubbleTimer);
1788
+ bubble.textContent = text;
1789
+ var row = el("div", "dp-bubble-replies", "");
1790
+ replies.forEach(function(label, index) {
1791
+ var answer = el("button", "dp-reply", label);
1792
+ answer.type = "button";
1793
+ answer.addEventListener("click", function(event) {
1794
+ event.stopPropagation();
1795
+ bubble.hidden = true;
1796
+ if (bubbleTimer !== null) window.clearTimeout(bubbleTimer);
1797
+ bubbleTimer = null;
1798
+ onReply(index);
1799
+ });
1800
+ row.appendChild(answer);
1801
+ });
1802
+ bubble.appendChild(row);
1803
+ bubble.hidden = false;
1804
+ bubbleTimer = window.setTimeout(function() {
1805
+ bubble.hidden = true;
1806
+ bubbleTimer = null;
1807
+ }, 6e3);
1808
+ }
1809
+ function toast(text) {
1810
+ var node = el("div", "dp-toast", text);
1811
+ card.insertBefore(node, card.firstChild);
1812
+ window.setTimeout(function() {
1813
+ node.remove();
1814
+ }, 4800);
1815
+ }
1816
+ function dispose() {
1817
+ if (reactTimer !== null) window.clearTimeout(reactTimer);
1818
+ if (bubbleTimer !== null) window.clearTimeout(bubbleTimer);
1819
+ reactTimer = null;
1820
+ bubbleTimer = null;
1821
+ }
1822
+ return { react, burst, flash, showBubble, showLine, toast, dispose };
1823
+ }
1824
+
1825
+ // src/client/io.js
1826
+ function createIo(ctx) {
1827
+ var actionSeq = 0;
1828
+ async function send(action, extra) {
1829
+ if (ctx.busy || ctx.stopped) return;
1830
+ if (ctx.view.pig === null && action !== "hatch") return;
1831
+ actionSeq += 1;
1832
+ ctx.busy = true;
1833
+ ctx.flash(action);
1834
+ try {
1835
+ var body = { action };
1836
+ if (extra) for (var k in extra) body[k] = extra[k];
1837
+ var res = await fetch(ACT_URL, {
1838
+ method: "POST",
1839
+ headers: { "content-type": "application/json" },
1840
+ body: JSON.stringify(body)
1841
+ });
1842
+ var next = await res.json();
1843
+ ctx.render(next);
1844
+ if (next && next.ok === false) {
1845
+ if (next.reason === "stale-line") return;
1846
+ if (next.reason === "silent") return;
1847
+ ctx.react("refuse", 520);
1848
+ if (next.reason === "no-item") {
1849
+ var emptyKind = str(next.kind, "");
1850
+ ctx.showBubble(NO_ITEM_LINE[emptyKind] ?? "\u80CC\u5305\u91CC\u6CA1\u6709\u80FD\u7528\u7684\u4E1C\u897F", 3200);
1851
+ return;
1852
+ }
1853
+ var reasons = {
1854
+ box: "\u5148\u628A\u7EB8\u76D2\u62C6\u5F00",
1855
+ "coronation-ineligible": "\u52A0\u5195\u6761\u4EF6\u8FD8\u6CA1\u9F50",
1856
+ cooldown: "\u8FD8\u8981\u7B49 " + num(next.wait, 0) + " \u79D2",
1857
+ poor: "\u94B1\u4E0D\u591F",
1858
+ away: "\u5B83\u5728\u5916\u9762",
1859
+ weak: "\u592A\u865A\u5F31\u4E86\uFF0C\u5148\u517B\u597D\u518D\u51FA\u95E8",
1860
+ hungry: "\u592A\u997F\u4E86",
1861
+ "wrong-medicine": "\u836F\u4E0D\u5BF9\u75C7\uFF0C\u75C5\u60C5\u52A0\u91CD\u4E86\u2026",
1862
+ empty: "\u80CC\u5305\u91CC\u6CA1\u6709",
1863
+ "not-sick": "\u5B83\u6CA1\u751F\u75C5",
1864
+ dead: "\u5B83\u5DF2\u7ECF\u8D70\u4E86\u2026",
1865
+ idle: "\u5B83\u6CA1\u5728\u5916\u9762",
1866
+ owned: "\u8FD9\u4EF6\u5DF2\u7ECF\u6709\u4E86",
1867
+ "low-level": "\u7B49\u7EA7\u4E0D\u591F\uFF08\u8981 Lv." + num(next.need, 0) + "\uFF0C\u73B0\u5728 Lv." + num(next.have, 0) + "\uFF09",
1868
+ "not-owned": "\u8FD8\u6CA1\u6709\u8FD9\u4EF6\u4E1C\u897F",
1869
+ "not-consumable": "\u8FD9\u4E2A\u662F\u7A7F\u7684\uFF0C\u4E0D\u662F\u7528\u7684",
1870
+ "wrong-stage": "\u8FD9\u4E2A\u5B66\u6BB5\u6CA1\u6709\u8FD9\u95E8\u8BFE",
1871
+ underqualified: "\u5B83\u8FD8\u6CA1\u8FD9\u4E2A\u672C\u4E8B\uFF0C\u5148\u53BB\u4E0A\u8BFE"
1872
+ };
1873
+ ctx.showBubble(reasons[next.reason] ?? "\u8FD9\u4E2A\u64CD\u4F5C\u6CA1\u6210", 2400);
1874
+ }
1875
+ } catch (error) {
1876
+ ctx.showBubble("\u64CD\u4F5C\u6CA1\u9001\u5230\u5BBF\u4E3B", 2600);
1877
+ ctx.react("refuse", 520);
1878
+ } finally {
1879
+ ctx.busy = false;
1880
+ }
1881
+ }
1882
+ async function refresh2() {
1883
+ if (ctx.stopped) return;
1884
+ ctx.fitPanel();
1885
+ var startedAt = actionSeq;
1886
+ try {
1887
+ var res = await fetch(STATE_URL, { cache: "no-store" });
1888
+ if (!res.ok) throw new Error("HTTP " + res.status);
1889
+ var next = await res.json();
1890
+ if (startedAt !== actionSeq) return;
1891
+ ctx.render(next);
1892
+ } catch (error) {
1893
+ if (ctx.stopped) return;
1894
+ ctx.showBubble("\u8FDE\u63A5\u4E0D\u4E0A\u5BBF\u4E3B", 4e3);
1895
+ }
1896
+ }
1897
+ return { send, refresh: refresh2 };
1898
+ }
1899
+
1900
+ // src/client/layout.js
1901
+ function createLayout(ctx) {
1902
+ function clampPig() {
1903
+ var vw = window.innerWidth || 0;
1904
+ var vh = window.innerHeight || 0;
1905
+ if (vw <= 0 || vh <= 0) return;
1906
+ var pigRect = ctx.pig.getBoundingClientRect ? ctx.pig.getBoundingClientRect() : null;
1907
+ var w = (pigRect ? pigRect.width || 0 : 0) + 2 * PIG_PADDING_X;
1908
+ var sceneRect = ctx.scene.getBoundingClientRect ? ctx.scene.getBoundingClientRect() : null;
1909
+ var h = Math.max(sceneRect ? sceneRect.height || 0 : 0, SCENE_RESERVE);
1910
+ var right = Math.min(Math.max(4, ctx.userRight), Math.max(4, vw - w - 4));
1911
+ var bottom = Math.min(Math.max(4, ctx.userBottom), Math.max(4, vh - h - 4));
1912
+ ctx.host.style.right = Math.round(right) + "px";
1913
+ ctx.host.style.bottom = Math.round(bottom) + "px";
1914
+ }
1915
+ function fitPanel() {
1916
+ if (!ctx.isOpen) return;
1917
+ var vw = window.innerWidth || 0;
1918
+ var vh = window.innerHeight || 0;
1919
+ if (vw <= 0 || vh <= 0) return;
1920
+ var rect = ctx.scene.getBoundingClientRect();
1921
+ var roomAbove = rect.top - PANEL_GAP - PANEL_MARGIN;
1922
+ var roomBelow = vh - rect.bottom - PANEL_GAP - PANEL_MARGIN;
1923
+ if (roomAbove >= roomBelow) {
1924
+ ctx.card.style.top = "auto";
1925
+ ctx.card.style.bottom = "calc(100% + " + PANEL_GAP + "px)";
1926
+ ctx.card.style.maxHeight = Math.max(PANEL_MIN_HEIGHT, Math.round(roomAbove)) + "px";
1927
+ } else {
1928
+ ctx.card.style.bottom = "auto";
1929
+ ctx.card.style.top = "calc(100% + " + PANEL_GAP + "px)";
1930
+ ctx.card.style.maxHeight = Math.max(PANEL_MIN_HEIGHT, Math.round(roomBelow)) + "px";
1931
+ }
1932
+ var width = Math.min(PANEL_WIDTH, vw - 2 * PANEL_MARGIN);
1933
+ ctx.card.style.maxWidth = Math.round(width) + "px";
1934
+ var shift = PANEL_MARGIN - (rect.right - width);
1935
+ ctx.card.style.right = shift > 0 ? -Math.round(shift) + "px" : "0px";
1936
+ var cardLeft = Math.max(rect.right - width, PANEL_MARGIN);
1937
+ ctx.hud.style.left = Math.max(9, Math.round(cardLeft - rect.left)) + "px";
1938
+ }
1939
+ function visibleTabs() {
1940
+ return ctx.devMode ? TABS.concat([DEV_TAB]) : TABS;
1941
+ }
1942
+ function paintBar() {
1943
+ while (ctx.bar.firstChild) ctx.bar.removeChild(ctx.bar.firstChild);
1944
+ var list = visibleTabs();
1945
+ for (var t = 0; t < list.length; t += 1) ctx.buildIcon(list[t]);
1946
+ if (ctx.icons[ctx.tab] === void 0 && ctx.tab !== "home") ctx.tab = "home";
1947
+ for (var k in ctx.icons) ctx.icons[k].setAttribute("data-active", k === ctx.tab ? "true" : "false");
1948
+ }
1949
+ function buildIcon(entry) {
1950
+ (function(entry2) {
1951
+ var btn = button("dp-ico", { "data-tab": entry2.key }, function() {
1952
+ if (ctx.host.getAttribute("data-open") !== "true") ctx.setOpen(true);
1953
+ ctx.select(entry2.key);
1954
+ });
1955
+ btn.appendChild(el("span", "dp-ico-e", entry2.emoji));
1956
+ btn.appendChild(el("span", null, entry2.label));
1957
+ ctx.icons[entry2.key] = btn;
1958
+ ctx.bar.appendChild(btn);
1959
+ })(entry);
1960
+ }
1961
+ return { clampPig, fitPanel, visibleTabs, paintBar, buildIcon };
1962
+ }
1963
+
1964
+ // src/client/normalize.js
1965
+ function normalize(raw) {
1966
+ var d = obj(raw);
1967
+ var pig = isObj(d.pig) ? d.pig : null;
1968
+ var legacy = pig !== null && !("coins" in pig) && !("health" in pig);
1969
+ return {
1970
+ legacy,
1971
+ // Host build version, shown in the debug tab so a stale bundle is
1972
+ // visible instead of being guessed at.
1973
+ version: str(d.version, ""),
1974
+ // Trust the flag when the host sends one. Older hosts did not, and for
1975
+ // those "a pig exists" is still the right answer.
1976
+ hatched: d.hatched === true || d.hatched === void 0 && pig !== null,
1977
+ dead: d.dead === true || pig !== null && num(pig.health, 5) <= 0,
1978
+ pig: pig === null ? null : {
1979
+ name: str(pig.name, "\u732A\u732A"),
1980
+ // The pig is measured in days now; `stage` carries how big it is and
1981
+ // what it looks like.
1982
+ stage: {
1983
+ key: str(obj(pig.stage).key, "piglet"),
1984
+ label: str(obj(pig.stage).label, "\u5C0F\u732A"),
1985
+ emoji: str(obj(pig.stage).emoji, "\u{1F416}"),
1986
+ size: num(obj(pig.stage).size, 56),
1987
+ line: str(obj(pig.stage).line, ""),
1988
+ art: typeof obj(pig.stage).art === "string" && obj(pig.stage).art !== "" ? obj(pig.stage).art : null,
1989
+ faded: obj(pig.stage).faded === true,
1990
+ // 加冕后的形态:有没有动作立绘、盖住哪些装扮位置。
1991
+ actionArt: obj(pig.stage).actionArt === true,
1992
+ hides: arr(obj(pig.stage).hides).map(function(slot) {
1993
+ return str(slot, "");
1994
+ })
1995
+ },
1996
+ // Older hosts send no sex; the HUD then simply shows none.
1997
+ sex: isObj(pig.sex) ? { key: str(pig.sex.key, ""), label: str(pig.sex.label, ""), symbol: str(pig.sex.symbol, "") } : null,
1998
+ ageLabel: str(pig.ageLabel, ""),
1999
+ ageForced: pig.ageForced === true,
2000
+ daysToNextStage: typeof pig.daysToNextStage === "number" ? pig.daysToNextStage : null,
2001
+ soul: pig.soul === true,
2002
+ mood: str(pig.mood, "fine"),
2003
+ moodEmoji: str(pig.moodEmoji, "\u{1F60A}"),
2004
+ moodLabel: str(pig.moodLabel, "\u8FD8\u4E0D\u9519"),
2005
+ satiety: Math.round(num(pig.satiety, 0)),
2006
+ happiness: Math.round(num(pig.happiness, 0)),
2007
+ cleanliness: Math.round(num(pig.cleanliness, 0)),
2008
+ health: num(pig.health, 5),
2009
+ healthPercent: num(pig.healthPercent, 100),
2010
+ coins: num(pig.coins, 0),
2011
+ weight: str(pig.weight, "\u2014"),
2012
+ xp: num(pig.xp, 0),
2013
+ // Level is driven by growth and decides the body (B2).
2014
+ level: (function(info) {
2015
+ var i = obj(info);
2016
+ var t = obj(i.title);
2017
+ return {
2018
+ level: num(i.level, 1),
2019
+ percent: num(i.percent, 0),
2020
+ toNext: num(i.toNext, 0),
2021
+ maxed: i.maxed === true,
2022
+ titleLabel: str(t.label, "\u65B0\u6765\u7684"),
2023
+ titleEmoji: str(t.emoji, "\u{1F331}")
2024
+ };
2025
+ })(pig.levelInfo),
2026
+ stageLine: str(pig.stageLine, ""),
2027
+ illness: isObj(pig.illness) ? {
2028
+ name: str(pig.illness.name, "\u751F\u75C5"),
2029
+ cure: str(pig.illness.cure, "\u836F"),
2030
+ cureEmoji: str(pig.illness.cureEmoji, "\u{1F48A}"),
2031
+ stage: num(pig.illness.stage, 1),
2032
+ doctorFee: typeof pig.illness.doctorFee === "number" ? pig.illness.doctorFee : null
2033
+ } : null,
2034
+ traits: {
2035
+ intel: num(obj(pig.traits).intel, 0),
2036
+ charm: num(obj(pig.traits).charm, 0),
2037
+ strong: num(obj(pig.traits).strong, 0)
2038
+ },
2039
+ courses: obj(pig.courses),
2040
+ // Souvenirs are objects now (rarity + story). An old host sent bare
2041
+ // strings, and those must still list rather than turn into [object
2042
+ // Object] or vanish.
2043
+ souvenirs: arr(pig.souvenirs).map((entry) => {
2044
+ if (typeof entry === "string") {
2045
+ return { key: entry, emoji: "\u{1F381}", label: entry, rarityLabel: "\u666E\u901A", rarityEmoji: "\u26AA", price: 0, story: "", fromLabel: "" };
2046
+ }
2047
+ return {
2048
+ key: str(obj(entry).key, ""),
2049
+ emoji: str(obj(entry).emoji, "\u{1F381}"),
2050
+ label: str(obj(entry).label, "\u7EAA\u5FF5\u54C1"),
2051
+ rarityLabel: str(obj(entry).rarityLabel, "\u666E\u901A"),
2052
+ rarityEmoji: str(obj(entry).rarityEmoji, "\u26AA"),
2053
+ price: num(obj(entry).price, 0),
2054
+ story: str(obj(entry).story, ""),
2055
+ fromLabel: str(obj(entry).fromLabel, "")
2056
+ };
2057
+ }).filter((entry) => entry.key !== ""),
2058
+ memories: arr(pig.memories).filter((m) => typeof m === "string")
2059
+ },
2060
+ actions: normalizeActions(d.actions),
2061
+ jobs: arr(d.jobs).map((job) => ({
2062
+ key: str(obj(job).key, ""),
2063
+ label: str(obj(job).label, "\u5DE5\u4F5C"),
2064
+ emoji: str(obj(job).emoji, "\u{1F4BC}"),
2065
+ minutes: num(obj(job).minutes, 0),
2066
+ coins: num(obj(job).coins, 0),
2067
+ available: obj(job).available === true,
2068
+ // What schooling has bought this job.
2069
+ traitLabel: str(obj(job).traitLabel, ""),
2070
+ traitEmoji: str(obj(job).traitEmoji, ""),
2071
+ traitPoints: num(obj(job).traitPoints, 0),
2072
+ baseMinutes: num(obj(job).baseMinutes, 0),
2073
+ baseCoins: num(obj(job).baseCoins, 0),
2074
+ payPercent: num(obj(job).payPercent, 0),
2075
+ speedPercent: num(obj(job).speedPercent, 0),
2076
+ // An old host has no gate at all, so a missing flag must read as
2077
+ // "qualified" — the opposite default would lock every job on upgrade.
2078
+ qualified: obj(job).qualified !== false,
2079
+ lockText: str(obj(job).lockText, ""),
2080
+ level: num(obj(job).level, 1),
2081
+ trait: str(obj(job).trait, ""),
2082
+ satiety: num(obj(job).satiety, 0),
2083
+ cleanliness: num(obj(job).cleanliness, 0),
2084
+ requirements: arr(obj(job).requirements).filter(isObj).map((entry) => ({
2085
+ text: str(entry.text, ""),
2086
+ need: num(entry.need, 0),
2087
+ have: num(entry.have, 0),
2088
+ kind: str(entry.kind, ""),
2089
+ met: entry.met === true
2090
+ }))
2091
+ })).filter((job) => job.key !== ""),
2092
+ // B4: nine subjects, each with its own lesson count and stage.
2093
+ subjects: arr(d.subjects).map((sub) => ({
2094
+ key: str(obj(sub).key, ""),
2095
+ label: str(obj(sub).label, "\u8BFE"),
2096
+ emoji: str(obj(sub).emoji, "\u{1F4D8}"),
2097
+ traitLabel: str(obj(sub).traitLabel, ""),
2098
+ traitEmoji: str(obj(sub).traitEmoji, ""),
2099
+ lessons: num(obj(sub).lessons, num(obj(sub).level, 0)),
2100
+ stageKey: str(obj(obj(sub).stage).key, ""),
2101
+ stageLabel: str(obj(obj(sub).stage).label, ""),
2102
+ graduatedLabel: isObj(obj(sub).graduated) ? str(obj(sub).graduated.label, "") : "",
2103
+ nextGraduation: typeof obj(sub).nextGraduation === "number" ? obj(sub).nextGraduation : null,
2104
+ minutes: num(obj(sub).minutes, 0),
2105
+ tuition: num(obj(sub).tuition, 0),
2106
+ gain: num(obj(sub).gain, 0),
2107
+ secondaryGain: num(obj(sub).secondaryGain, 0),
2108
+ available: obj(sub).available === true,
2109
+ affordable: obj(sub).affordable !== false
2110
+ })).filter((sub) => sub.key !== ""),
2111
+ // 兴趣课:学习页里随时能学的一栏,学完加的是既有的三条属性。
2112
+ interests: arr(d.interests).map((entry) => ({
2113
+ key: str(obj(entry).key, ""),
2114
+ label: str(obj(entry).label, "\u5174\u8DA3"),
2115
+ emoji: str(obj(entry).emoji, "\u{1F3AF}"),
2116
+ traitLabel: str(obj(entry).traitLabel, ""),
2117
+ traitEmoji: str(obj(entry).traitEmoji, ""),
2118
+ minutes: num(obj(entry).minutes, 0),
2119
+ cost: num(obj(entry).cost, 0),
2120
+ gain: num(obj(entry).gain, 0),
2121
+ blurb: str(obj(entry).blurb, ""),
2122
+ times: num(obj(entry).times, 0),
2123
+ certificate: str(obj(entry).certificate, ""),
2124
+ certificateAfter: num(obj(entry).certificateAfter, 0),
2125
+ certified: obj(entry).certified === true,
2126
+ available: obj(entry).available === true,
2127
+ affordable: obj(entry).affordable === true
2128
+ })).filter((entry) => entry.key !== ""),
2129
+ stages: arr(d.stages).map((stage) => ({
2130
+ key: str(obj(stage).key, ""),
2131
+ label: str(obj(stage).label, "\u5B66\u6BB5"),
2132
+ emoji: str(obj(stage).emoji, "\u{1F4DA}"),
2133
+ minutes: num(obj(stage).minutes, 0),
2134
+ tuition: num(obj(stage).tuition, 0),
2135
+ gain: num(obj(stage).gain, 0),
2136
+ // B4: the lesson numbers this stage covers (upTo null = no end).
2137
+ from: num(obj(stage).from, 0),
2138
+ upTo: typeof obj(stage).upTo === "number" ? obj(stage).upTo : null,
2139
+ // Which courses this stage teaches — empty on an old host, in which
2140
+ // case the panel shows every subject rather than none.
2141
+ subjects: arr(obj(stage).subjects).filter((key) => typeof key === "string"),
2142
+ // The school ladder: a stage with `unlocked === false` is gated behind
2143
+ // finishing the previous one, and says by how much.
2144
+ unlocked: obj(stage).unlocked !== false,
2145
+ progress: isObj(obj(stage).progress) ? {
2146
+ done: num(obj(stage).progress.done, 0),
2147
+ need: num(obj(stage).progress.need, 0),
2148
+ label: str(obj(stage).progress.label, "")
2149
+ } : null
2150
+ })).filter((stage) => stage.key !== ""),
2151
+ trips: arr(d.trips).map((trip) => ({
2152
+ key: str(obj(trip).key, ""),
2153
+ label: str(obj(trip).label, "\u76EE\u7684\u5730"),
2154
+ emoji: str(obj(trip).emoji, "\u{1F9F3}"),
2155
+ minutes: num(obj(trip).minutes, 0),
2156
+ cost: num(obj(trip).cost, 0),
2157
+ happiness: num(obj(trip).happiness, 0),
2158
+ // What the destination can bring back — the far trips advertise it.
2159
+ souvenirCount: num(obj(trip).souvenirCount, 0),
2160
+ bestRarity: str(obj(trip).bestRarity, ""),
2161
+ bestRarityEmoji: str(obj(trip).bestRarityEmoji, ""),
2162
+ affordable: obj(trip).affordable === true,
2163
+ available: obj(trip).available === true
2164
+ })).filter((trip) => trip.key !== ""),
2165
+ // 家当: owned and worn, never counted. An old host sends none.
2166
+ dress: arr(d.dress).map((entry) => ({
2167
+ key: str(obj(entry).key, ""),
2168
+ label: str(obj(entry).label, "\u88C5\u626E"),
2169
+ emoji: str(obj(entry).emoji, "\u{1F455}"),
2170
+ price: num(obj(entry).price, 0),
2171
+ level: num(obj(entry).level, 1),
2172
+ slot: str(obj(entry).slot, ""),
2173
+ slotLabel: str(obj(entry).slotLabel, ""),
2174
+ blurb: str(obj(entry).blurb, ""),
2175
+ owned: obj(entry).owned === true,
2176
+ worn: obj(entry).worn === true,
2177
+ unlocked: obj(entry).unlocked !== false
2178
+ })).filter((entry) => entry.key !== ""),
2179
+ shop: arr(d.shop).map((item) => ({
2180
+ key: str(obj(item).key, ""),
2181
+ label: str(obj(item).label, "\u7269\u54C1"),
2182
+ emoji: str(obj(item).emoji, "\u{1F4E6}"),
2183
+ price: num(obj(item).price, 0),
2184
+ kind: str(obj(item).kind, "food"),
2185
+ tier: typeof obj(item).tier === "number" ? obj(item).tier : null,
2186
+ // 家当 fields: a dress item is owned (not counted) or waits for a level.
2187
+ level: typeof obj(item).level === "number" ? obj(item).level : null,
2188
+ owned: obj(item).owned === true,
2189
+ worn: obj(item).worn === true,
2190
+ unlocked: obj(item).unlocked !== false,
2191
+ blurb: str(obj(item).blurb, ""),
2192
+ affordable: obj(item).affordable === true,
2193
+ needed: obj(item).needed === true
2194
+ })).filter((item) => item.key !== ""),
2195
+ inventory: obj(d.inventory),
2196
+ daily: {
2197
+ canSignIn: obj(d.daily).canSignIn === true,
2198
+ signInDay: num(obj(d.daily).signInDay, 1),
2199
+ signInTotal: num(obj(d.daily).signInTotal, 0),
2200
+ cycle: num(obj(d.daily).cycle, 12),
2201
+ unclaimed: num(obj(d.daily).unclaimed, 0),
2202
+ onlineMinutes: num(obj(d.daily).onlineMinutes, 0)
2203
+ },
2204
+ // 新到旧;老宿主没有 diary 时是空数组,面板不显示这一栏。
2205
+ diary: arr(d.diary).map((entry) => ({
2206
+ day: str(obj(entry).day, ""),
2207
+ text: str(obj(entry).text, "")
2208
+ })).filter((entry) => entry.day !== "" && entry.text !== ""),
2209
+ // Which items each care action could spend right now.
2210
+ care: (() => {
2211
+ const out = {};
2212
+ const source = obj(d.care);
2213
+ for (const action of ["feed", "bathe", "play"]) {
2214
+ out[action] = arr(source[action]).map((entry) => ({
2215
+ key: str(obj(entry).key, ""),
2216
+ label: str(obj(entry).label, "\u7269\u54C1"),
2217
+ emoji: str(obj(entry).emoji, "\u{1F4E6}"),
2218
+ default: obj(entry).default === true,
2219
+ count: typeof obj(entry).count === "number" ? obj(entry).count : null,
2220
+ satiety: num(obj(entry).satiety, 0),
2221
+ happiness: num(obj(entry).happiness, 0),
2222
+ cleanliness: num(obj(entry).cleanliness, 0)
2223
+ })).filter((entry) => entry.key !== "");
2224
+ }
2225
+ return out;
2226
+ })(),
2227
+ activity: isObj(d.activity) ? {
2228
+ kind: str(d.activity.kind, "work"),
2229
+ key: str(d.activity.key, ""),
2230
+ label: str(d.activity.label, "\u5916\u9762"),
2231
+ emoji: str(d.activity.emoji, "\u{1F4BC}"),
2232
+ secondsLeft: num(d.activity.secondsLeft, 0),
2233
+ progress: num(d.activity.progress, 0)
2234
+ } : null,
2235
+ canGoOut: d.canGoOut === true,
2236
+ timeScale: num(d.timeScale, 1),
2237
+ boxStage: isObj(d.boxStage) ? {
2238
+ key: str(d.boxStage.key, "box"),
2239
+ label: str(d.boxStage.label, "\u7EB8\u76D2"),
2240
+ emoji: str(d.boxStage.emoji, "\u{1F4E6}"),
2241
+ size: num(d.boxStage.size, 58)
2242
+ } : { key: "box", label: "\u7EB8\u76D2", emoji: "\u{1F4E6}", size: 58 },
2243
+ awayBlocked: typeof d.awayBlocked === "string" ? d.awayBlocked : null,
2244
+ // B9: the villager card. Older hosts send none, and the card says so.
2245
+ profile: isObj(d.profile) ? {
2246
+ personality: isObj(d.profile.personality) ? { label: str(d.profile.personality.label, ""), emoji: str(d.profile.personality.emoji, "") } : null,
2247
+ catchphrase: str(d.profile.catchphrase, ""),
2248
+ motto: str(d.profile.motto, ""),
2249
+ birthday: str(d.profile.birthday, ""),
2250
+ zodiac: isObj(d.profile.zodiac) ? { label: str(d.profile.zodiac.label, ""), emoji: str(d.profile.zodiac.emoji, "") } : null,
2251
+ counts: {
2252
+ days: num(obj(d.profile.counts).days, 0),
2253
+ certificates: num(obj(d.profile.counts).certificates, 0),
2254
+ souvenirs: num(obj(d.profile.counts).souvenirs, 0),
2255
+ graduations: num(obj(d.profile.counts).graduations, 0)
2256
+ }
2257
+ } : null,
2258
+ // 加冕: the forms and how close the pig is. Older hosts send none.
2259
+ forms: isObj(d.forms) ? {
2260
+ current: typeof d.forms.current === "string" ? d.forms.current : null,
2261
+ forms: arr(d.forms.forms).map(function(raw2) {
2262
+ var f = obj(raw2);
2263
+ return {
2264
+ key: str(f.key, ""),
2265
+ label: str(f.label, ""),
2266
+ emoji: str(f.emoji, "\u{1F451}"),
2267
+ art: str(f.art, ""),
2268
+ current: f.current === true,
2269
+ ready: f.ready === true,
2270
+ requirements: arr(f.requirements).map(function(row) {
2271
+ var r = obj(row);
2272
+ return { key: str(r.key, ""), label: str(r.label, ""), have: num(r.have, 0), need: num(r.need, 0), met: r.met === true };
2273
+ })
2274
+ };
2275
+ })
2276
+ } : null,
2277
+ // B6: what the pig calls its owner, and 免打扰. Older hosts send neither.
2278
+ dialogue: {
2279
+ ownerName: str(obj(d.dialogue).ownerName, "\u4E3B\u4EBA"),
2280
+ quiet: obj(d.dialogue).quiet === true
2281
+ },
2282
+ pending: arr(d.pending).filter((e) => isObj(e) && typeof e.at === "number").map((e) => ({
2283
+ id: num(e.id, 0),
2284
+ kind: str(e.kind, ""),
2285
+ text: str(e.text, ""),
2286
+ at: e.at,
2287
+ replies: arr(e.replies).filter((label) => typeof label === "string")
2288
+ })),
2289
+ maxHealth: num(d.maxHealth, 5)
2290
+ };
2291
+ }
2292
+ function normalizeActions(raw) {
2293
+ var source = obj(raw);
2294
+ var out = {};
2295
+ for (var i = 0; i < MODES.length; i += 1) {
2296
+ var key = MODES[i];
2297
+ var entry = obj(source[key]);
2298
+ out[key] = {
2299
+ ready: entry.ready !== false,
2300
+ waitSeconds: num(entry.waitSeconds, 0),
2301
+ blocked: typeof entry.blocked === "string" ? entry.blocked : null
2302
+ };
2303
+ }
2304
+ return out;
2305
+ }
2306
+
2307
+ // src/client/storage.js
2308
+ function readStore(key) {
2309
+ try {
2310
+ var value = window.localStorage.getItem(key);
2311
+ return value !== null ? value : window.localStorage.getItem(key.replace(/^dsh-piggy:/, "dsh-pig:"));
2312
+ } catch (error) {
2313
+ return null;
2314
+ }
2315
+ }
2316
+ function writeStore(key, value) {
2317
+ try {
2318
+ window.localStorage.setItem(key, value);
2319
+ } catch (error) {
2320
+ }
2321
+ }
2322
+
2323
+ // src/client/tabs/card.js
2324
+ var LIMITS = { catchphrase: 6, motto: 24 };
2325
+ function renderCardTab(ui) {
2326
+ var p = ui.view.pig;
2327
+ var profile = ui.view.profile;
2328
+ if (p === null) return;
2329
+ if (profile === null) {
2330
+ ui.content.appendChild(el("div", "dp-empty", "\u91CD\u542F dsh \u4E4B\u540E\u624D\u6709\u5C45\u6C11\u5361"));
2331
+ return;
2332
+ }
2333
+ var card = el("div", "dp-vcard");
2334
+ card.setAttribute("data-sex", p.sex !== null ? p.sex.key : "none");
2335
+ var top = el("div", "dp-vcard-top");
2336
+ var avatar = el("div", "dp-vcard-avatar");
2337
+ if (p.stage.art !== null) {
2338
+ var img = (
2339
+ /** @type {HTMLImageElement} */
2340
+ el("img", "dp-vcard-img")
2341
+ );
2342
+ img.src = ART_URL + p.stage.art + ".svg";
2343
+ img.alt = "";
2344
+ avatar.appendChild(img);
2345
+ } else {
2346
+ avatar.appendChild(el("span", "dp-vcard-e", p.stage.emoji));
2347
+ }
2348
+ top.appendChild(avatar);
2349
+ var who = el("div", "dp-vcard-who");
2350
+ who.appendChild(el("b", "dp-vcard-name", p.name + (p.sex !== null ? " " + p.sex.symbol : "")));
2351
+ who.appendChild(el("span", "dp-vcard-sub", "Lv." + p.level.level + " " + p.level.titleEmoji + p.level.titleLabel));
2352
+ who.appendChild(el("span", "dp-vcard-sub", p.stage.label));
2353
+ top.appendChild(who);
2354
+ card.appendChild(top);
2355
+ card.appendChild(field("\u751F\u65E5", profile.birthday));
2356
+ if (profile.zodiac !== null) card.appendChild(field("\u661F\u5EA7", profile.zodiac.emoji + " " + profile.zodiac.label));
2357
+ if (profile.personality !== null) card.appendChild(field("\u6027\u683C", profile.personality.emoji + " " + profile.personality.label));
2358
+ var forms = ui.view.forms;
2359
+ var worn = forms === null ? null : forms.forms.find(function(f) {
2360
+ return f.current;
2361
+ }) || null;
2362
+ if (worn !== null) card.appendChild(field("\u5F62\u6001", worn.emoji + " " + worn.label));
2363
+ card.appendChild(editableField(ui, "catchphrase", "\u53E3\u5934\u7985", profile.catchphrase));
2364
+ card.appendChild(editableField(ui, "motto", "\u7B7E\u540D", profile.motto));
2365
+ var c = profile.counts;
2366
+ card.appendChild(el(
2367
+ "div",
2368
+ "dp-vcard-foot",
2369
+ "\u517B\u4E86 " + c.days + " \u5929 \xB7 \u8BC1\u4E66 " + c.certificates + " \xB7 \u7EAA\u5FF5\u54C1 " + c.souvenirs + " \xB7 \u6BD5\u4E1A " + c.graduations
2370
+ ));
2371
+ ui.content.appendChild(card);
2372
+ }
2373
+ function field(label, value) {
2374
+ var row = el("div", "dp-vcard-row");
2375
+ row.appendChild(el("span", "dp-vcard-label", label + "\uFF1A"));
2376
+ row.appendChild(el("span", "dp-vcard-value", value));
2377
+ return row;
2378
+ }
2379
+ function editableField(ui, key, label, value) {
2380
+ var editing = ui.cardEdit !== null && ui.cardEdit.field === key;
2381
+ var row = el("div", "dp-vcard-row" + (key === "motto" ? " dp-vcard-motto-row" : ""));
2382
+ row.appendChild(el("span", "dp-vcard-label", label + "\uFF1A"));
2383
+ if (editing) {
2384
+ var input = (
2385
+ /** @type {HTMLInputElement} */
2386
+ el("input", "dp-input dp-vcard-input")
2387
+ );
2388
+ input.value = ui.cardEdit.draft;
2389
+ input.maxLength = LIMITS[key];
2390
+ input.setAttribute("data-card-input", key);
2391
+ input.addEventListener("input", function() {
2392
+ ui.cardEdit = { field: key, draft: input.value };
2393
+ });
2394
+ var save = button("dp-mini", { "data-card-save": key }, function() {
2395
+ var text = (ui.cardEdit === null ? "" : ui.cardEdit.draft).trim();
2396
+ ui.cardEdit = null;
2397
+ if (text !== "") ui.send(key, { text });
2398
+ ui.renderContent();
2399
+ });
2400
+ save.textContent = "\u597D";
2401
+ var cancel = button("dp-mini dp-mini-plain", { "data-card-cancel": key }, function() {
2402
+ ui.cardEdit = null;
2403
+ ui.renderContent();
2404
+ });
2405
+ cancel.textContent = "\u7B97\u4E86";
2406
+ row.appendChild(input);
2407
+ row.appendChild(save);
2408
+ row.appendChild(cancel);
2409
+ return row;
2410
+ }
2411
+ row.appendChild(el("span", key === "motto" ? "dp-vcard-value dp-vcard-motto" : "dp-vcard-value", key === "motto" ? "\u300C" + value + "\u300D" : value));
2412
+ var pencil = button("dp-vcard-edit", { "data-card-edit": key }, function() {
2413
+ ui.cardEdit = { field: key, draft: value };
2414
+ ui.renderContent();
2415
+ });
2416
+ pencil.textContent = "\u270F\uFE0F";
2417
+ pencil.title = "\u6539" + label;
2418
+ row.appendChild(pencil);
2419
+ return row;
2420
+ }
2421
+
2422
+ // src/client/tabs/crown.js
2423
+ function renderCrownTab(ui) {
2424
+ var forms = ui.view.forms;
2425
+ if (forms === null) {
2426
+ ui.content.appendChild(el("div", "dp-empty", "\u91CD\u542F dsh \u4E4B\u540E\u624D\u6709\u52A0\u5195"));
2427
+ return;
2428
+ }
2429
+ if (ui.view.dead) ui.content.appendChild(el("div", "dp-empty", "\u5B83\u8D70\u4E86\uFF0C\u6551\u56DE\u6765\u624D\u80FD\u52A0\u5195"));
2430
+ for (var f = 0; f < forms.forms.length; f += 1) ui.content.appendChild(formBlock(ui, forms.forms[f]));
2431
+ }
2432
+ function formBlock(ui, form) {
2433
+ var box = el("div", form.current ? "dp-crown dp-crown-now" : "dp-crown");
2434
+ box.setAttribute("data-form", form.key);
2435
+ var top = el("div", "dp-crown-top");
2436
+ var pic = el("div", "dp-crown-pic");
2437
+ if (form.art !== "") {
2438
+ var img = (
2439
+ /** @type {HTMLImageElement} */
2440
+ el("img", "dp-crown-img")
2441
+ );
2442
+ img.src = ART_URL + form.art + ".svg";
2443
+ img.alt = "";
2444
+ pic.appendChild(img);
2445
+ } else {
2446
+ pic.appendChild(el("span", null, form.emoji));
2447
+ }
2448
+ top.appendChild(pic);
2449
+ var side = el("div", "dp-crown-side");
2450
+ side.appendChild(el("div", "dp-crown-head", form.emoji + " " + form.label));
2451
+ if (form.current) {
2452
+ side.appendChild(el("div", "dp-crown-done", "\u2713 \u5F53\u524D\u5F62\u6001"));
2453
+ } else {
2454
+ var chips = el("div", "dp-crown-reqs");
2455
+ for (var r = 0; r < form.requirements.length; r += 1) {
2456
+ var row = form.requirements[r];
2457
+ chips.appendChild(el(
2458
+ "span",
2459
+ row.met ? "dp-crown-req dp-crown-ok" : "dp-crown-req",
2460
+ (row.met ? "\u2713 " : "\u2717 ") + row.label + " " + Math.min(row.have, row.need) + "/" + row.need
2461
+ ));
2462
+ }
2463
+ side.appendChild(chips);
2464
+ }
2465
+ top.appendChild(side);
2466
+ box.appendChild(top);
2467
+ if (!form.current) {
2468
+ var go = button("dp-btn dp-btn-wide", { "data-crown": form.key }, function() {
2469
+ ui.send("crown", { form: form.key });
2470
+ });
2471
+ go.textContent = form.ready ? "\u{1F451} \u52A0\u5195" : "\u6761\u4EF6\u9F50\u4E86\u5C31\u80FD\u52A0\u5195";
2472
+ go.disabled = !form.ready;
2473
+ box.appendChild(go);
2474
+ }
2475
+ return box;
2476
+ }
2477
+
2478
+ // src/client/tabs/home.js
2479
+ var APP_COLOR = {
2480
+ status: "green",
2481
+ card: "pink",
2482
+ crown: "purple",
2483
+ study: "yellow",
2484
+ work: "orange",
2485
+ shop: "red",
2486
+ travel: "blue",
2487
+ bag: "teal",
2488
+ update: "lime",
2489
+ dev: "brown"
2490
+ };
2491
+ function renderHome(ui, apps) {
2492
+ var p = ui.view.pig;
2493
+ var head = el("div", "dp-title");
2494
+ head.appendChild(el("b", null, p.name + (p.sex !== null ? " " + p.sex.symbol : "") + " Lv." + p.level.level));
2495
+ head.appendChild(el("span", null, "\u{1FA99} " + p.coins));
2496
+ ui.content.appendChild(head);
2497
+ var grid = tileGrid();
2498
+ for (var i = 0; i < apps.length; i += 1) {
2499
+ (function(app) {
2500
+ grid.appendChild(tile({
2501
+ emoji: app.emoji,
2502
+ label: app.label,
2503
+ color: APP_COLOR[app.key] ?? "blue",
2504
+ tag: alertFor(ui, app.key),
2505
+ data: { "data-app": app.key },
2506
+ onPick: function() {
2507
+ ui.select(app.key);
2508
+ }
2509
+ }));
2510
+ })(apps[i]);
2511
+ }
2512
+ ui.content.appendChild(grid);
2513
+ }
2514
+ function alertFor(ui, key) {
2515
+ var p = ui.view.pig;
2516
+ if (key === "status") {
2517
+ if (ui.view.dead) return "\u8D70\u4E86";
2518
+ if (p.illness !== null) return "\u751F\u75C5";
2519
+ if (ui.view.activity !== null) return "\u5728\u5916\u9762";
2520
+ return "";
2521
+ }
2522
+ var icon = ui.icons[key];
2523
+ return icon !== void 0 && icon.getAttribute("data-alert") === "true" ? "!" : "";
2524
+ }
2525
+ function appHeader(ui, app, info) {
2526
+ var row = el("div", "dp-drill dp-app-head");
2527
+ var back = el("button", "dp-drill-back");
2528
+ back.setAttribute("data-home", "true");
2529
+ back.textContent = "\u2039";
2530
+ back.addEventListener("click", function(event) {
2531
+ if (event && typeof event.stopPropagation === "function") event.stopPropagation();
2532
+ ui.select("home");
2533
+ });
2534
+ row.appendChild(back);
2535
+ row.appendChild(el("b", "dp-drill-title", app.emoji + " " + app.label));
2536
+ if (info) row.appendChild(el("span", "dp-drill-info", info));
2537
+ ui.content.appendChild(row);
2538
+ }
2539
+
2540
+ // src/client/tabs/update.js
2541
+ var state = {
2542
+ current: null,
2543
+ list: null,
2544
+ error: null,
2545
+ loading: false,
2546
+ pick: null,
2547
+ busy: null,
2548
+ fraction: 0,
2549
+ message: null,
2550
+ listening: false
2551
+ };
2552
+ function updatesBridge() {
2553
+ var shell = (
2554
+ /** @type {any} */
2555
+ window.piggyShell
2556
+ );
2557
+ return shell && shell.updates ? shell : null;
2558
+ }
2559
+ function refresh(ui) {
2560
+ var shell = updatesBridge();
2561
+ if (shell === null || state.loading) return;
2562
+ state.loading = true;
2563
+ state.error = null;
2564
+ if (!state.listening) {
2565
+ state.listening = true;
2566
+ shell.updates.onProgress(function(fraction) {
2567
+ state.fraction = fraction;
2568
+ ui.renderContent();
2569
+ });
2570
+ }
2571
+ Promise.all([shell.updates.current(), shell.updates.list()]).then(function(got) {
2572
+ state.current = got[0];
2573
+ if (got[1] && got[1].ok) state.list = got[1].releases;
2574
+ else state.error = got[1] && got[1].reason || "\u6CA1\u95EE\u5230";
2575
+ }, function() {
2576
+ state.error = "\u6CA1\u95EE\u5230";
2577
+ }).then(function() {
2578
+ state.loading = false;
2579
+ ui.renderContent();
2580
+ });
2581
+ }
2582
+ function install(ui, version) {
2583
+ var shell = updatesBridge();
2584
+ if (shell === null) return;
2585
+ state.busy = version;
2586
+ state.fraction = 0;
2587
+ state.message = null;
2588
+ ui.renderContent();
2589
+ shell.updates.install(version).then(function(result) {
2590
+ state.message = result.ok ? "\u6362\u597D\u4E86\uFF0C\u732A\u9A6C\u4E0A\u56DE\u6765\u2026" : result.reason;
2591
+ if (!result.ok) state.busy = null;
2592
+ ui.renderContent();
2593
+ });
2594
+ }
2595
+ function rollback(ui) {
2596
+ var shell = updatesBridge();
2597
+ if (shell === null) return;
2598
+ state.busy = "rollback";
2599
+ ui.renderContent();
2600
+ shell.updates.rollback().then(function(result) {
2601
+ state.message = result.ok ? "\u56DE\u53BB\u4E86\uFF0C\u732A\u9A6C\u4E0A\u56DE\u6765\u2026" : result.reason;
2602
+ if (!result.ok) state.busy = null;
2603
+ ui.renderContent();
2604
+ });
2605
+ }
2606
+ function renderUpdateTab(ui) {
2607
+ if (updatesBridge() === null) {
2608
+ ui.content.appendChild(el("div", "dp-empty", "\u684C\u9762\u7248\u624D\u6709\u8FD9\u4E2A"));
2609
+ return;
2610
+ }
2611
+ if (state.current === null && state.list === null && state.error === null) refresh(ui);
2612
+ var cur = state.current;
2613
+ var head = el("div", "dp-pick dp-tile-card dp-update-now");
2614
+ head.appendChild(el("div", "dp-pick-head", cur === null ? "\u6B63\u5728\u770B\u73B0\u5728\u7684\u7248\u672C\u2026" : "\u73B0\u5728 v" + cur.version + (cur.bundled ? "\uFF08\u5B89\u88C5\u5305\u81EA\u5E26\uFF09" : "")));
2615
+ if (cur !== null) head.appendChild(el("div", "dp-dim", "\u5B89\u88C5\u5305 " + cur.shell));
2616
+ if (state.message !== null) head.appendChild(el("div", "dp-req", state.message));
2617
+ if (state.busy !== null && state.message === null) {
2618
+ head.appendChild(el("div", "dp-dim", state.busy === "rollback" ? "\u6B63\u5728\u6362\u56DE\u53BB\u2026" : "\u4E0B\u8F7D\u4E2D " + Math.round(state.fraction * 100) + "%"));
2619
+ }
2620
+ var latest = state.list === null ? null : state.list.find(function(r) {
2621
+ return r.blocked === null && !r.prerelease;
2622
+ }) || null;
2623
+ if (latest !== null && cur !== null && !latest.current) {
2624
+ var up = button("dp-btn dp-btn-wide", { "data-update-latest": latest.version }, function() {
2625
+ install(ui, latest.version);
2626
+ });
2627
+ up.textContent = "\u2B06\uFE0F \u66F4\u65B0\u5230\u6700\u65B0 v" + latest.version;
2628
+ up.disabled = state.busy !== null;
2629
+ head.appendChild(up);
2630
+ } else if (latest !== null) {
2631
+ head.appendChild(el("div", "dp-req dp-req-ok", "\u2713 \u5DF2\u7ECF\u662F\u6700\u65B0"));
2632
+ }
2633
+ ui.content.appendChild(head);
2634
+ if (state.loading && state.list === null) ui.content.appendChild(el("div", "dp-empty", "\u6B63\u5728\u95EE GitHub\u2026"));
2635
+ if (state.error !== null) {
2636
+ ui.content.appendChild(el("div", "dp-empty", state.error));
2637
+ var again = button("dp-btn dp-btn-wide", { "data-update-retry": "" }, function() {
2638
+ refresh(ui);
2639
+ });
2640
+ again.textContent = "\u518D\u8BD5\u4E00\u6B21";
2641
+ ui.content.appendChild(again);
2642
+ }
2643
+ if (state.list !== null) renderList(ui, state.list);
2644
+ if (cur !== null && cur.previous !== null) {
2645
+ var back = button("dp-btn dp-btn-wide dp-update-back", { "data-update-rollback": "" }, function() {
2646
+ rollback(ui);
2647
+ });
2648
+ back.textContent = "\u21A9\uFE0F \u56DE\u5230\u4E0A\u4E00\u4E2A\u7248\u672C v" + cur.previous;
2649
+ back.disabled = state.busy !== null;
2650
+ ui.content.appendChild(back);
2651
+ }
2652
+ }
2653
+ function renderList(ui, list) {
2654
+ if (list.length === 0) {
2655
+ ui.content.appendChild(el("div", "dp-empty", "GitHub \u4E0A\u8FD8\u6CA1\u6709\u80FD\u70ED\u66F4\u65B0\u7684\u7248\u672C"));
2656
+ return;
2657
+ }
2658
+ var grid = tileGrid();
2659
+ var picked = null;
2660
+ for (var i = 0; i < list.length; i += 1) {
2661
+ (function(release, first) {
2662
+ var active = state.pick === release.version;
2663
+ if (active) picked = release;
2664
+ grid.appendChild(tile({
2665
+ emoji: release.current ? "\u{1F416}" : "\u{1F4E6}",
2666
+ label: "v" + release.version,
2667
+ color: "lime",
2668
+ soft: true,
2669
+ active,
2670
+ note: release.date,
2671
+ badge: first && release.blocked === null ? "\u65B0" : "",
2672
+ tag: release.current ? "\u5728\u7528" : release.blocked !== null ? "\u{1F512}" : "",
2673
+ dim: release.blocked !== null,
2674
+ data: { "data-release": release.version },
2675
+ onPick: function() {
2676
+ state.pick = active ? null : release.version;
2677
+ ui.renderContent();
2678
+ }
2679
+ }));
2680
+ })(list[i], i === 0);
2681
+ }
2682
+ ui.content.appendChild(grid);
2683
+ if (picked !== null) ui.content.appendChild(details(ui, picked));
2684
+ }
2685
+ function details(ui, release) {
2686
+ var box = el("div", "dp-pick dp-tile-card dp-update-detail");
2687
+ box.appendChild(el("div", "dp-pick-head", "v" + release.version + (release.date ? " \xB7 " + release.date : "") + (release.prerelease ? " \xB7 \u9884\u89C8\u7248" : "")));
2688
+ if (release.notes) box.appendChild(el("div", "dp-update-notes", release.notes));
2689
+ var go = button("dp-btn dp-btn-wide", { "data-update-install": release.version }, function() {
2690
+ if (release.blocked === "shell") updatesBridge().openPage(release.page);
2691
+ else install(ui, release.version);
2692
+ });
2693
+ if (release.current) {
2694
+ go.textContent = "\u6B63\u5728\u7528\u8FD9\u4E2A";
2695
+ go.disabled = true;
2696
+ } else if (release.blocked === "shell") {
2697
+ box.appendChild(el("div", "dp-req", "\u2717 \u8981\u5148\u88C5 " + release.minShell + " \u4EE5\u4E0A\u7684\u5B89\u88C5\u5305"));
2698
+ go.textContent = "\u53BB\u4E0B\u8F7D\u65B0\u5B89\u88C5\u5305";
2699
+ } else if (release.blocked === "save") {
2700
+ box.appendChild(el("div", "dp-req", "\u2717 \u5B58\u6863\u592A\u65B0\uFF0C\u8FD9\u4E2A\u7248\u672C\u8BFB\u4E0D\u4E86"));
2701
+ go.textContent = "\u6362\u4E0D\u4E86";
2702
+ go.disabled = true;
2703
+ } else {
2704
+ go.textContent = "\u6362\u5230\u8FD9\u4E2A\u7248\u672C";
2705
+ go.disabled = state.busy !== null;
2706
+ }
2707
+ box.appendChild(go);
2708
+ return box;
2709
+ }
2710
+
2711
+ // src/client/panel.js
2712
+ var URGENT_KINDS = ["sick", "worse", "death", "cured", "revived"];
2713
+ function createPanel(ctx) {
2714
+ var AWAY_LINE = {
2715
+ work: "\u5728\u5FD9",
2716
+ study: "\u5728\u5FF5\u4E66",
2717
+ trip: "\u5728\u8DEF\u4E0A"
2718
+ };
2719
+ function setOpen(next) {
2720
+ ctx.isOpen = next;
2721
+ ctx.host.setAttribute("data-open", next ? "true" : "false");
2722
+ ctx.card.hidden = !next;
2723
+ ctx.hud.hidden = !next;
2724
+ if (!next) ctx.bubble.hidden = true;
2725
+ writeStore(OPEN_KEY, next ? "true" : "false");
2726
+ if (next) {
2727
+ renderContent();
2728
+ ctx.fitPanel();
2729
+ } else {
2730
+ ctx.card.style.right = "";
2731
+ ctx.card.style.top = "auto";
2732
+ ctx.card.style.bottom = "";
2733
+ ctx.card.style.maxHeight = "";
2734
+ ctx.card.style.maxWidth = "";
2735
+ }
2736
+ }
2737
+ function select(next) {
2738
+ ctx.tab = next;
2739
+ ctx.picker = null;
2740
+ if (next in ctx.drill) {
2741
+ ctx.drill[next] = null;
2742
+ ctx.drill.pick = null;
2743
+ }
2744
+ renderContent();
2745
+ for (var k in ctx.icons) ctx.icons[k].setAttribute("data-active", k === ctx.tab ? "true" : "false");
2746
+ }
2747
+ function renderContent() {
2748
+ var scrollTop = ctx.content.scrollTop;
2749
+ paintContent();
2750
+ ctx.content.scrollTop = scrollTop;
2751
+ }
2752
+ function paintContent() {
2753
+ ctx.content.textContent = "";
2754
+ for (var k = 0; k < TABS.length; k += 1) {
2755
+ ctx.icons[TABS[k].key].setAttribute("data-active", TABS[k].key === ctx.tab ? "true" : "false");
2756
+ }
2757
+ if (ctx.host.getAttribute("data-open") !== "true") return;
2758
+ if (ctx.view.legacy) {
2759
+ var legacy = el("div", "dp-alert dp-legacy");
2760
+ legacy.appendChild(el("b", null, "\u26A0\uFE0F \u5BBF\u4E3B\u662F\u65E7\u7248\u672C"));
2761
+ legacy.appendChild(el("div", null, "\u91D1\u5E01\u3001\u5065\u5EB7\u3001\u6253\u5DE5\u3001\u5546\u5E97\u8FD9\u4E9B\u662F\u65B0\u589E\u7684\uFF0C\u91CD\u542F dsh\uFF08\u4E0D\u662F\u5237\u65B0\u9875\u9762\uFF09\u4E4B\u540E\u624D\u4F1A\u51FA\u73B0\u3002"));
2762
+ ctx.content.appendChild(legacy);
2763
+ }
2764
+ if (ctx.view.pig === null) {
2765
+ ctx.content.appendChild(el("div", "dp-empty", "\u95E8\u53E3\u653E\u7740\u4E00\u4E2A\u7EB8\u76D2\uFF0C\u91CC\u9762\u7AB8\u7AB8\u7AA3\u7AA3 \u{1F4E6}"));
2766
+ var grid = el("div", "dp-actions");
2767
+ var hatch = button("dp-btn dp-btn-wide", { "data-action": "hatch" }, function() {
2768
+ ctx.send("hatch");
2769
+ });
2770
+ hatch.appendChild(el("span", null, "\u{1F95A}"));
2771
+ hatch.appendChild(el("span", null, "\u62C6\u5F00\u7EB8\u76D2"));
2772
+ grid.appendChild(hatch);
2773
+ ctx.content.appendChild(grid);
2774
+ return;
2775
+ }
2776
+ var apps = TABS.concat(updatesBridge() !== null ? [UPDATE_TAB] : [], ctx.devMode ? [DEV_TAB] : []);
2777
+ if (ctx.tab === "home") {
2778
+ renderHome(ctx, apps);
2779
+ ctx.fitPanel();
2780
+ return;
2781
+ }
2782
+ var drilled = ctx.tab in ctx.drill && ctx.drill[ctx.tab] !== null;
2783
+ var app = apps.find(function(entry) {
2784
+ return entry.key === ctx.tab;
2785
+ });
2786
+ if (app !== void 0 && !drilled) appHeader(ctx, app, ctx.tab === "shop" ? "\u{1FA99} " + ctx.view.pig.coins : "");
2787
+ if (ctx.tab === "status") renderStatusTab(ctx);
2788
+ else if (ctx.tab === "card") renderCardTab(ctx);
2789
+ else if (ctx.tab === "crown") renderCrownTab(ctx);
2790
+ else if (ctx.tab === "study") renderStudyTab(ctx);
2791
+ else if (ctx.tab === "work") renderWorkTab(ctx);
2792
+ else if (ctx.tab === "shop") renderShopTab(ctx);
2793
+ else if (ctx.tab === "travel") renderTravelTab(ctx);
2794
+ else if (ctx.tab === "dev") renderDevTab(ctx);
2795
+ else if (ctx.tab === "update") renderUpdateTab(ctx);
2796
+ else renderBagTab(ctx);
2797
+ ctx.fitPanel();
2798
+ }
2799
+ function render(next) {
2800
+ ctx.view = normalize(next);
2801
+ var stageEntry = null;
2802
+ var firstOpen = null;
2803
+ for (var s = 0; s < ctx.view.stages.length; s += 1) {
2804
+ var entry = ctx.view.stages[s];
2805
+ if (entry.unlocked !== false && firstOpen === null) firstOpen = entry.key;
2806
+ if (entry.key === ctx.stage) stageEntry = entry;
2807
+ }
2808
+ if (!ctx.stagePicked && firstOpen !== null && (stageEntry === null || stageEntry.unlocked === false)) ctx.stage = firstOpen;
2809
+ ctx.host.setAttribute("data-dead", ctx.view.dead ? "true" : "false");
2810
+ ctx.host.setAttribute("data-open", ctx.isOpen ? "true" : "false");
2811
+ ctx.host.setAttribute("data-dev", ctx.devMode ? "true" : "false");
2812
+ ctx.host.setAttribute("data-away", ctx.view.activity === null ? "false" : ctx.view.activity.kind);
2813
+ if (ctx.view.activity === null) {
2814
+ ctx.work.hidden = true;
2815
+ } else {
2816
+ ctx.work.hidden = false;
2817
+ ctx.prop.textContent = ctx.view.activity.emoji;
2818
+ ctx.progressFill.style.width = ctx.view.activity.progress + "%";
2819
+ ctx.work.setAttribute("data-kind", ctx.view.activity.kind);
2820
+ ctx.work.title = (AWAY_LINE[ctx.view.activity.kind] ?? "\u5728\u5916\u9762") + "\uFF1A" + ctx.view.activity.label;
2821
+ }
2822
+ if (ctx.view.hatched !== true) {
2823
+ ctx.pigArt.hidden = true;
2824
+ ctx.pigArt.removeAttribute("src");
2825
+ ctx.pigEmoji.hidden = false;
2826
+ ctx.pigEmoji.textContent = ctx.view.boxStage.emoji;
2827
+ ctx.pig.removeAttribute("data-art");
2828
+ ctx.pig.setAttribute("data-mood", "box");
2829
+ ctx.host.style.setProperty("--pig-size", ctx.view.boxStage.size + "px");
2830
+ ctx.soul.hidden = true;
2831
+ ctx.host.setAttribute("data-soul", "false");
2832
+ ctx.host.setAttribute("data-faded", "false");
2833
+ ctx.host.setAttribute("data-unhatched", "true");
2834
+ ctx.pokeHint.hidden = false;
2835
+ ctx.hudName.textContent = "\u4E00\u4E2A" + ctx.view.boxStage.label;
2836
+ ctx.hudCoins.textContent = "\u70B9\u5F00\u62C6\u5F00\u5B83";
2837
+ ctx.hudHealth.textContent = "";
2838
+ ctx.lastStage = null;
2839
+ } else {
2840
+ const pigStage = ctx.view.pig.stage;
2841
+ if (pigStage.art !== null) {
2842
+ ctx.pigArt.hidden = false;
2843
+ ctx.pigEmoji.hidden = true;
2844
+ ctx.pig.setAttribute("data-art", pigStage.art);
2845
+ ctx.pig.setAttribute("data-art-actions", pigStage.actionArt ? "true" : "false");
2846
+ ctx.host.setAttribute("data-art-actions", pigStage.actionArt ? "true" : "false");
2847
+ ctx.pig.setAttribute("data-activity", ctx.view.activity === null ? "" : ctx.view.activity.kind);
2848
+ syncPigArt(ctx.pig, ctx.pigArt);
2849
+ } else {
2850
+ ctx.pigArt.hidden = true;
2851
+ ctx.pigArt.removeAttribute("src");
2852
+ ctx.pigEmoji.hidden = false;
2853
+ ctx.pigEmoji.textContent = pigStage.emoji;
2854
+ ctx.pig.removeAttribute("data-art");
2855
+ ctx.pig.removeAttribute("data-art-actions");
2856
+ ctx.host.removeAttribute("data-art-actions");
2857
+ }
2858
+ ctx.host.style.setProperty("--pig-size", pigStage.size + "px");
2859
+ ctx.pig.setAttribute("data-mood", ctx.view.pig.mood);
2860
+ ctx.host.setAttribute("data-soul", ctx.view.pig.soul ? "true" : "false");
2861
+ ctx.host.setAttribute("data-faded", pigStage.faded ? "true" : "false");
2862
+ ctx.host.setAttribute("data-unhatched", "false");
2863
+ ctx.pokeHint.hidden = true;
2864
+ ctx.soul.hidden = ctx.view.pig.soul !== true;
2865
+ ctx.pig.setAttribute("data-stage", pigStage.key);
2866
+ ctx.dressSlots.textContent = "";
2867
+ for (var wd = 0; wd < ctx.view.dress.length; wd += 1) {
2868
+ var piece = ctx.view.dress[wd];
2869
+ if (!piece.worn || piece.slot === "") continue;
2870
+ if (pigStage.hides.indexOf(piece.slot) >= 0) continue;
2871
+ var node = el("span", "dp-slot", piece.emoji);
2872
+ node.setAttribute("data-slot", piece.slot);
2873
+ ctx.dressSlots.appendChild(node);
2874
+ }
2875
+ ctx.hudName.textContent = ctx.view.pig.name + (ctx.view.pig.sex !== null ? " " + ctx.view.pig.sex.symbol : "") + " Lv." + ctx.view.pig.level.level + " \xB7 " + pigStage.label + (ctx.view.pig.ageLabel ? " \xB7 " + ctx.view.pig.ageLabel : "") + (ctx.view.pig.ageForced ? " \u{1F527}" : "");
2876
+ ctx.hudCoins.textContent = "\u{1FA99} " + ctx.view.pig.coins;
2877
+ ctx.hudHealth.textContent = "\u{1F49A} " + ctx.view.pig.health + "/" + ctx.view.maxHealth;
2878
+ if (ctx.lastStage !== null && pigStage.key !== ctx.lastStage) {
2879
+ ctx.react("levelup", 950);
2880
+ ctx.burst(["\u2728", "\u{1F389}", "\u2B50"], 4);
2881
+ ctx.showBubble("\u6211\u957F\u5927\u5566\uFF01" + pigStage.emoji, 2600);
2882
+ }
2883
+ ctx.lastStage = pigStage.key;
2884
+ }
2885
+ var daily = ctx.view.daily;
2886
+ var dailyAction = daily.canSignIn ? "signIn" : daily.unclaimed > 0 ? "openGift" : null;
2887
+ ctx.dailyHint.hidden = dailyAction === null || ctx.view.pig === null;
2888
+ if (dailyAction !== null) {
2889
+ ctx.dailyHint.textContent = dailyAction === "signIn" ? "\u{1F4C5}" : "\u{1F381}";
2890
+ ctx.dailyHint.title = dailyAction === "signIn" ? "\u7B7E\u5230\u7B2C " + daily.signInDay + "/" + daily.cycle + " \u5929" : "\u6709 " + daily.unclaimed + " \u4E2A\u5728\u7EBF\u793C\u5305";
2891
+ ctx.dailyHint.setAttribute("data-action", dailyAction);
2892
+ }
2893
+ var studyStage = null;
2894
+ for (var st = 0; st < ctx.view.stages.length; st += 1) {
2895
+ if (ctx.view.stages[st].key === ctx.stage) studyStage = ctx.view.stages[st];
2896
+ }
2897
+ var studyOpen = ctx.view.canGoOut && (studyStage === null || studyStage.unlocked !== false);
2898
+ var hasCourse = studyOpen && ctx.view.subjects.some(function(subject) {
2899
+ var onStage = studyStage === null || studyStage.subjects.length === 0 || studyStage.subjects.indexOf(subject.key) >= 0;
2900
+ return onStage && subject.affordable;
2901
+ });
2902
+ ctx.icons.study.setAttribute("data-alert", hasCourse ? "true" : "false");
2903
+ ctx.icons.shop.setAttribute("data-alert", ctx.view.pig !== null && ctx.view.pig.illness !== null ? "true" : "false");
2904
+ ctx.icons.crown.setAttribute("data-alert", ctx.view.forms !== null && ctx.view.forms.forms.some(function(f) {
2905
+ return f.ready;
2906
+ }) ? "true" : "false");
2907
+ ctx.icons.travel.setAttribute("data-alert", ctx.view.pig !== null && ctx.view.pig.coins >= 400 ? "true" : "false");
2908
+ for (var i = 0; i < ctx.view.pending.length; i += 1) {
2909
+ var event = ctx.view.pending[i];
2910
+ if (event.id > 0 ? event.id <= ctx.lastPendingId : event.at <= ctx.lastPendingAt) continue;
2911
+ if (event.id > 0) ctx.lastPendingId = event.id;
2912
+ ctx.lastPendingAt = Math.max(ctx.lastPendingAt, event.at);
2913
+ if (event.kind === "line") {
2914
+ showPigLine(event);
2915
+ continue;
2916
+ }
2917
+ if (ctx.view.dialogue.quiet && URGENT_KINDS.indexOf(event.kind) < 0) continue;
2918
+ ctx.toast(str(event.text, "\u732A\u6709\u65B0\u6D88\u606F"));
2919
+ if (event.kind === "coronation") {
2920
+ ctx.react("levelup", 950);
2921
+ ctx.burst(["\u{1F451}", "\u2728"], 3);
2922
+ } else if (event.kind === "levelup") {
2923
+ ctx.react("levelup", 950);
2924
+ ctx.burst(["\u2728", "\u{1F389}"], 3);
2925
+ } else if (event.kind === "cured") {
2926
+ ctx.react("cure", 900);
2927
+ ctx.burst(["\u{1F49A}", "\u2728"], 3);
2928
+ } else if (event.kind === "death") ctx.react("refuse", 700);
2929
+ else if (event.kind === "work") {
2930
+ ctx.react("away", 900);
2931
+ ctx.burst(["\u{1FA99}", "\u{1F4B0}"], 3);
2932
+ } else if (event.kind === "study") {
2933
+ ctx.react("away", 900);
2934
+ ctx.burst(["\u{1F4DA}", "\u2728"], 3);
2935
+ } else if (event.kind === "trip") {
2936
+ ctx.react("away", 900);
2937
+ ctx.burst(["\u{1F9F3}", "\u{1F381}"], 3);
2938
+ }
2939
+ }
2940
+ if ((ctx.ownerEdit !== null || ctx.pigNameEdit !== null) && ctx.tab === "status") return;
2941
+ if (ctx.cardEdit !== null && ctx.tab === "card") return;
2942
+ renderContent();
2943
+ }
2944
+ function showPigLine(event) {
2945
+ var lineId = event.id;
2946
+ ctx.showLine(event.text, event.replies, function(index) {
2947
+ ctx.send("reply", { line: lineId, index });
2948
+ });
2949
+ }
2950
+ return { setOpen, select, renderContent, render };
2951
+ }
2952
+
2953
+ // src/client/scene.js
2954
+ function createScene() {
2955
+ var font = document.createElement("link");
2956
+ font.rel = "stylesheet";
2957
+ font.href = "https://fonts.googleapis.com/css2?family=Nunito:wght@400;500;600;700;800;900&family=Noto+Sans+SC:wght@400;500;700&display=swap";
2958
+ document.head.appendChild(font);
2959
+ var style = document.createElement("style");
2960
+ style.textContent = CSS;
2961
+ document.head.appendChild(style);
2962
+ var host = document.createElement("div");
2963
+ host.setAttribute(MOUNTED, "");
2964
+ var card = el("div", "dp-card");
2965
+ var scene = el("div", "dp-scene");
2966
+ var hud = el("div", "dp-hud");
2967
+ var hudName = el("div", null, "\u732A\u732A");
2968
+ var hudCoins = el("div", null, "\u{1FA99} 0");
2969
+ var hudHealth = el("div", null, "\u{1F49A} 5/5");
2970
+ hud.appendChild(hudName);
2971
+ hud.appendChild(hudCoins);
2972
+ hud.appendChild(hudHealth);
2973
+ scene.appendChild(hud);
2974
+ var bubble = el("div", "dp-bubble", "");
2975
+ bubble.hidden = true;
2976
+ scene.appendChild(bubble);
2977
+ var work = el("div", "dp-work");
2978
+ var prop = el("span", "dp-prop", "\u{1F4BC}");
2979
+ var progressWrap = el("div", "dp-progress");
2980
+ var progressFill = document.createElement("i");
2981
+ progressWrap.appendChild(progressFill);
2982
+ work.appendChild(prop);
2983
+ work.appendChild(progressWrap);
2984
+ work.hidden = true;
2985
+ scene.appendChild(work);
2986
+ var pokeHint = el("div", "dp-poke-hint");
2987
+ pokeHint.appendChild(el("span", null, "\u{1F446}"));
2988
+ pokeHint.appendChild(el("span", null, "\u6233\u4E09\u4E0B"));
2989
+ pokeHint.hidden = true;
2990
+ scene.appendChild(pokeHint);
2991
+ var dailyHint = el("button", "dp-daily");
2992
+ dailyHint.hidden = true;
2993
+ scene.appendChild(dailyHint);
2994
+ var soul = el("span", "dp-soul", "\u{1F47B}");
2995
+ soul.hidden = true;
2996
+ scene.appendChild(soul);
2997
+ var pigArt = document.createElement("img");
2998
+ pigArt.className = "dp-pig-img";
2999
+ pigArt.alt = "";
3000
+ pigArt.hidden = true;
3001
+ var pigEmoji = el("span", "dp-pig-emoji", "\u{1F416}");
3002
+ var pig = el("div", "dp-pig");
3003
+ pig.appendChild(pigArt);
3004
+ pig.appendChild(pigEmoji);
3005
+ var dressSlots = el("div", "dp-dress");
3006
+ pig.appendChild(dressSlots);
3007
+ scene.appendChild(pig);
3008
+ scene.title = "\u5DE6\u952E\u6478\u6478 \xB7 \u53F3\u952E\u6253\u5F00\u9762\u677F \xB7 \u62D6\u52A8\u53EF\u79FB\u52A8";
3009
+ var bar = el("div", "dp-bar");
3010
+ var content = el("div", "dp-content");
3011
+ card.appendChild(content);
3012
+ card.appendChild(bar);
3013
+ host.appendChild(card);
3014
+ host.appendChild(scene);
3015
+ if (document.body !== null && document.body !== void 0) {
3016
+ document.body.appendChild(host);
3017
+ } else {
3018
+ document.addEventListener("DOMContentLoaded", function() {
3019
+ try {
3020
+ document.body.appendChild(host);
3021
+ } catch (error) {
3022
+ }
3023
+ }, { once: true });
3024
+ }
3025
+ return { font, style, host, card, scene, hud, hudName, hudCoins, hudHealth, bubble, work, prop, progressWrap, progressFill, pokeHint, dailyHint, soul, pigArt, pigEmoji, pig, dressSlots, bar, content };
3026
+ }
3027
+
3028
+ // src/client/index.js
3029
+ window.__ModuleLoader__.load({
3030
+ id: "dsh-piggy",
3031
+ factory: (require2) => {
3032
+ var module = { exports: {} };
3033
+ var exports = module.exports;
3034
+ var devMode = false;
3035
+ function apply(ctx) {
3036
+ try {
3037
+ return mount();
3038
+ } catch (error) {
3039
+ console.warn("[dsh-piggy] \u6302\u8F7D\u5931\u8D25\uFF0C\u732A\u5148\u9000\u5230\u4E00\u8FB9", error);
3040
+ return () => {
3041
+ };
3042
+ }
3043
+ }
3044
+ function mount() {
3045
+ if (document.querySelector("[" + MOUNTED + "]") !== null) {
3046
+ console.warn("[dsh-piggy] \u5DF2\u5B58\u5728\u5B9E\u4F8B\uFF0C\u8DF3\u8FC7\u91CD\u590D\u6302\u8F7D");
3047
+ return () => {
3048
+ };
3049
+ }
3050
+ var parts = createScene();
3051
+ var {
3052
+ font,
3053
+ style,
3054
+ host,
3055
+ card,
3056
+ scene,
3057
+ hud,
3058
+ hudName,
3059
+ hudCoins,
3060
+ hudHealth,
3061
+ bubble,
3062
+ work,
3063
+ prop,
3064
+ progressWrap,
3065
+ progressFill,
3066
+ pokeHint,
3067
+ dailyHint,
3068
+ soul,
3069
+ pigArt,
3070
+ pigEmoji,
3071
+ pig,
3072
+ dressSlots,
3073
+ bar,
3074
+ content
3075
+ } = parts;
3076
+ var savedPos = readStore(POSITION_KEY);
3077
+ var userRight = 18;
3078
+ var userBottom = 18;
3079
+ if (savedPos !== null) {
3080
+ try {
3081
+ var parsed = JSON.parse(savedPos);
3082
+ if (parsed && typeof parsed.right === "number") userRight = parsed.right;
3083
+ if (parsed && typeof parsed.bottom === "number") userBottom = parsed.bottom;
3084
+ } catch (error) {
3085
+ }
3086
+ }
3087
+ host.style.right = userRight + "px";
3088
+ host.style.bottom = userBottom + "px";
3089
+ var icons = {};
3090
+ var view = normalize(null);
3091
+ var tab = "home";
3092
+ var stage = "primary";
3093
+ var stagePicked = false;
3094
+ var drill = { study: null, shop: null, bag: null, work: null, pick: null };
3095
+ var souvenirPick = null;
3096
+ var picker = null;
3097
+ var ownerEdit = null;
3098
+ var pigNameEdit = null;
3099
+ var cardEdit = null;
3100
+ var isOpen = readStore(OPEN_KEY) === "true";
3101
+ var lastStage = null;
3102
+ var lastPendingAt = 0;
3103
+ var lastPendingId = 0;
3104
+ var pollTimer = null;
3105
+ var fx = createEffects({
3106
+ scene,
3107
+ pig,
3108
+ pigArt,
3109
+ card,
3110
+ bubble,
3111
+ isStopped: function() {
3112
+ return stopped;
3113
+ }
3114
+ });
3115
+ var react = fx.react, burst = fx.burst, flash = fx.flash;
3116
+ var showBubble = fx.showBubble, showLine = fx.showLine, toast = fx.toast;
3117
+ var stopped = false;
3118
+ var busy = false;
3119
+ var ctx = {
3120
+ host,
3121
+ card,
3122
+ content,
3123
+ scene,
3124
+ hud,
3125
+ hudName,
3126
+ hudCoins,
3127
+ hudHealth,
3128
+ bubble,
3129
+ work,
3130
+ prop,
3131
+ progressWrap,
3132
+ progressFill,
3133
+ pokeHint,
3134
+ dailyHint,
3135
+ soul,
3136
+ pigArt,
3137
+ pigEmoji,
3138
+ pig,
3139
+ dressSlots,
3140
+ bar,
3141
+ icons,
3142
+ flash,
3143
+ react,
3144
+ burst,
3145
+ showBubble,
3146
+ showLine,
3147
+ toast,
3148
+ get view() {
3149
+ return view;
3150
+ },
3151
+ set view(next) {
3152
+ view = next;
3153
+ },
3154
+ get tab() {
3155
+ return tab;
3156
+ },
3157
+ set tab(next) {
3158
+ tab = next;
3159
+ },
3160
+ get stage() {
3161
+ return stage;
3162
+ },
3163
+ set stage(next) {
3164
+ stage = next;
3165
+ },
3166
+ get stagePicked() {
3167
+ return stagePicked;
3168
+ },
3169
+ set stagePicked(next) {
3170
+ stagePicked = next;
3171
+ },
3172
+ get drill() {
3173
+ return drill;
3174
+ },
3175
+ get picker() {
3176
+ return picker;
3177
+ },
3178
+ set picker(next) {
3179
+ picker = next;
3180
+ },
3181
+ get souvenirPick() {
3182
+ return souvenirPick;
3183
+ },
3184
+ set souvenirPick(next) {
3185
+ souvenirPick = next;
3186
+ },
3187
+ get ownerEdit() {
3188
+ return ownerEdit;
3189
+ },
3190
+ set ownerEdit(next) {
3191
+ ownerEdit = next;
3192
+ },
3193
+ get pigNameEdit() {
3194
+ return pigNameEdit;
3195
+ },
3196
+ set pigNameEdit(next) {
3197
+ pigNameEdit = next;
3198
+ },
3199
+ get cardEdit() {
3200
+ return cardEdit;
3201
+ },
3202
+ set cardEdit(next) {
3203
+ cardEdit = next;
3204
+ },
3205
+ get isOpen() {
3206
+ return isOpen;
3207
+ },
3208
+ set isOpen(next) {
3209
+ isOpen = next;
3210
+ },
3211
+ get lastStage() {
3212
+ return lastStage;
3213
+ },
3214
+ set lastStage(next) {
3215
+ lastStage = next;
3216
+ },
3217
+ get lastPendingAt() {
3218
+ return lastPendingAt;
3219
+ },
3220
+ set lastPendingAt(next) {
3221
+ lastPendingAt = next;
3222
+ },
3223
+ get lastPendingId() {
3224
+ return lastPendingId;
3225
+ },
3226
+ set lastPendingId(next) {
3227
+ lastPendingId = next;
3228
+ },
3229
+ get userRight() {
3230
+ return userRight;
3231
+ },
3232
+ set userRight(next) {
3233
+ userRight = next;
3234
+ },
3235
+ get userBottom() {
3236
+ return userBottom;
3237
+ },
3238
+ set userBottom(next) {
3239
+ userBottom = next;
3240
+ },
3241
+ get busy() {
3242
+ return busy;
3243
+ },
3244
+ set busy(next) {
3245
+ busy = next;
3246
+ },
3247
+ get stopped() {
3248
+ return stopped;
3249
+ },
3250
+ set stopped(next) {
3251
+ stopped = next;
3252
+ },
3253
+ get devMode() {
3254
+ return devMode;
3255
+ }
3256
+ };
3257
+ var layout = createLayout(ctx);
3258
+ var panel = createPanel(ctx);
3259
+ ctx.select = panel.select;
3260
+ ctx.setOpen = panel.setOpen;
3261
+ ctx.fitPanel = layout.fitPanel;
3262
+ ctx.paintBar = layout.paintBar;
3263
+ ctx.buildIcon = layout.buildIcon;
3264
+ ctx.clampPig = layout.clampPig;
3265
+ var render = panel.render, renderContent = panel.renderContent;
3266
+ var setOpen = panel.setOpen, select = panel.select;
3267
+ var fitPanel = layout.fitPanel, clampPig = layout.clampPig;
3268
+ var paintBar = layout.paintBar, buildIcon = layout.buildIcon;
3269
+ for (var t = 0; t < TABS.length; t += 1) buildIcon(TABS[t]);
3270
+ var io = createIo(ctx);
3271
+ var send = io.send, refresh2 = io.refresh;
3272
+ ctx.send = send;
3273
+ ctx.render = render;
3274
+ ctx.renderContent = renderContent;
3275
+ ctx.send = send;
3276
+ ctx.renderContent = renderContent;
3277
+ ctx.setOpen = setOpen;
3278
+ ctx.fitPanel = fitPanel;
3279
+ ctx.flash = flash;
3280
+ dailyHint.addEventListener("pointerdown", function(event) {
3281
+ event.stopPropagation();
3282
+ });
3283
+ dailyHint.addEventListener("click", function(event) {
3284
+ event.stopPropagation();
3285
+ var action = dailyHint.getAttribute("data-action");
3286
+ if (action !== null && action !== "") send(action);
3287
+ });
3288
+ var drag = null;
3289
+ scene.addEventListener("pointerdown", function(event) {
3290
+ if (event.button !== 0) return;
3291
+ drag = {
3292
+ x: event.clientX,
3293
+ y: event.clientY,
3294
+ right: parseFloat(getComputedStyle(host).right) || 18,
3295
+ bottom: parseFloat(getComputedStyle(host).bottom) || 18,
3296
+ moved: false
3297
+ };
3298
+ scene.setAttribute("data-dragging", "true");
3299
+ scene.setPointerCapture?.(event.pointerId);
3300
+ });
3301
+ scene.addEventListener("pointermove", function(event) {
3302
+ if (drag === null) return;
3303
+ var dx = event.clientX - drag.x;
3304
+ var dy = event.clientY - drag.y;
3305
+ if (Math.abs(dx) > 3 || Math.abs(dy) > 3) drag.moved = true;
3306
+ userRight = drag.right - dx;
3307
+ userBottom = drag.bottom - dy;
3308
+ clampPig();
3309
+ fitPanel();
3310
+ });
3311
+ function endDrag() {
3312
+ if (drag === null) return false;
3313
+ var moved = drag.moved;
3314
+ drag = null;
3315
+ scene.removeAttribute("data-dragging");
3316
+ clampPig();
3317
+ writeStore(POSITION_KEY, JSON.stringify({ right: userRight, bottom: userBottom }));
3318
+ fitPanel();
3319
+ return moved;
3320
+ }
3321
+ var boxPokes = 0;
3322
+ function pokeBox() {
3323
+ boxPokes += 1;
3324
+ react("poke", 560);
3325
+ if (boxPokes >= BOX_POKES_TO_OPEN) {
3326
+ boxPokes = 0;
3327
+ host.removeAttribute("data-poke");
3328
+ showBubble("\u54C7\u2014\u2014\uFF01", 1200);
3329
+ burst(["\u2728", "\u{1F389}", "\u{1F4A8}"], 6);
3330
+ send("hatch");
3331
+ return;
3332
+ }
3333
+ host.setAttribute("data-poke", String(boxPokes));
3334
+ burst(["\u{1F4A8}"], 2);
3335
+ showBubble(BOX_POKE_LINES[boxPokes - 1], 2200);
3336
+ }
3337
+ scene.addEventListener("pointerup", function() {
3338
+ if (endDrag()) return;
3339
+ if (view.hatched !== true) {
3340
+ pokeBox();
3341
+ return;
3342
+ }
3343
+ if (!view.dead) flash("pet");
3344
+ });
3345
+ scene.addEventListener("pointercancel", function() {
3346
+ endDrag();
3347
+ });
3348
+ scene.addEventListener("contextmenu", function(event) {
3349
+ event.preventDefault();
3350
+ setOpen(!isOpen);
3351
+ if (isOpen && view.pig !== null) flash("pet");
3352
+ });
3353
+ clampPig();
3354
+ setOpen(isOpen);
3355
+ render(view);
3356
+ refresh2();
3357
+ pollTimer = window.setInterval(refresh2, POLL_MS);
3358
+ var chatTimer = null;
3359
+ function scheduleChat() {
3360
+ var minutes = IDLE_CHAT_MINUTES.min + Math.random() * (IDLE_CHAT_MINUTES.max - IDLE_CHAT_MINUTES.min);
3361
+ chatTimer = window.setTimeout(function() {
3362
+ if (!stopped && !busy && view.pig !== null) send("chat", { reason: "idle" });
3363
+ scheduleChat();
3364
+ }, minutes * 6e4);
3365
+ }
3366
+ var greetTimer = window.setTimeout(function() {
3367
+ if (!stopped && view.pig !== null) send("chat", { reason: "enter" });
3368
+ }, GREET_DELAY_MS);
3369
+ scheduleChat();
3370
+ function onResize() {
3371
+ clampPig();
3372
+ fitPanel();
3373
+ }
3374
+ window.addEventListener?.("resize", onResize);
3375
+ function setDevMode(on) {
3376
+ devMode = on === true;
3377
+ writeStore(DEV_KEY, devMode ? "1" : "0");
3378
+ host.setAttribute("data-dev", devMode ? "true" : "false");
3379
+ paintBar();
3380
+ if (devMode) {
3381
+ setOpen(true);
3382
+ select("dev");
3383
+ showBubble("\u{1F527} \u5F00\u53D1\u8005\u6A21\u5F0F\u5DF2\u5F00", 2e3);
3384
+ } else {
3385
+ if (tab === "dev") select("status");
3386
+ showBubble("\u5F00\u53D1\u8005\u6A21\u5F0F\u5DF2\u5173", 1600);
3387
+ }
3388
+ }
3389
+ devMode = readStore(DEV_KEY) === "1";
3390
+ host.setAttribute("data-dev", devMode ? "true" : "false");
3391
+ if (devMode) paintBar();
3392
+ function onKeyDown(event) {
3393
+ if (event.ctrlKey && event.shiftKey && (event.key === "D" || event.key === "d")) {
3394
+ event.preventDefault();
3395
+ setDevMode(!devMode);
3396
+ }
3397
+ }
3398
+ window.addEventListener?.("keydown", onKeyDown);
3399
+ try {
3400
+ window.dshPigDev = {
3401
+ on: function() {
3402
+ setDevMode(true);
3403
+ },
3404
+ off: function() {
3405
+ setDevMode(false);
3406
+ },
3407
+ toggle: function() {
3408
+ setDevMode(!devMode);
3409
+ }
3410
+ };
3411
+ } catch (error) {
3412
+ }
3413
+ function dispose() {
3414
+ stopped = true;
3415
+ window.removeEventListener?.("resize", onResize);
3416
+ window.removeEventListener?.("keydown", onKeyDown);
3417
+ try {
3418
+ delete /** @type {any} */
3419
+ window.dshPigDev;
3420
+ } catch (error) {
3421
+ }
3422
+ if (pollTimer !== null) window.clearInterval(pollTimer);
3423
+ if (chatTimer !== null) window.clearTimeout(chatTimer);
3424
+ window.clearTimeout(greetTimer);
3425
+ fx.dispose();
3426
+ pollTimer = null;
3427
+ host.remove();
3428
+ style.remove();
3429
+ font.remove();
3430
+ }
3431
+ return dispose;
3432
+ }
3433
+ exports.name = "dsh-piggy";
3434
+ exports.apply = apply;
3435
+ return module.exports;
3436
+ }
3437
+ });
3438
+ })();