@linxin666/dsh-pet 0.3.22 → 0.3.24

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 (44) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +2 -1
  3. package/README.zh.md +2 -1
  4. package/lib/client.js +71 -9
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +14 -7
  7. package/lib/types/announce.d.ts +6 -6
  8. package/lib/types/announce.d.ts.map +1 -1
  9. package/lib/types/announce.js +5 -5
  10. package/lib/types/client/PetDockEntry.js +1 -1
  11. package/lib/types/client/PetSettingsCard.d.ts +4 -0
  12. package/lib/types/client/PetSettingsCard.d.ts.map +1 -1
  13. package/lib/types/client/PetSettingsCard.js +3 -1
  14. package/lib/types/client/PetSprite.d.ts +1 -1
  15. package/lib/types/client/PetSprite.d.ts.map +1 -1
  16. package/lib/types/client/PetSprite.js +40 -7
  17. package/lib/types/client/locales.d.ts +4 -0
  18. package/lib/types/client/locales.d.ts.map +1 -1
  19. package/lib/types/client/locales.js +4 -0
  20. package/lib/types/index.d.ts +2 -0
  21. package/lib/types/index.d.ts.map +1 -1
  22. package/lib/types/index.js +2 -1
  23. package/lib/types/persist.d.ts +32 -0
  24. package/lib/types/persist.d.ts.map +1 -1
  25. package/lib/types/persist.js +37 -0
  26. package/lib/types/service.d.ts +3 -1
  27. package/lib/types/service.d.ts.map +1 -1
  28. package/lib/types/service.js +4 -2
  29. package/package.json +1 -1
  30. package/src/announce.ts +6 -6
  31. package/src/client/PetDockEntry.test.tsx +1 -1
  32. package/src/client/PetDockEntry.tsx +1 -1
  33. package/src/client/PetSettingsCard.tsx +16 -0
  34. package/src/client/PetSprite.test.tsx +1 -1
  35. package/src/client/PetSprite.tsx +40 -7
  36. package/src/client/gameplay-hud.test.tsx +1 -1
  37. package/src/client/locales.ts +4 -0
  38. package/src/client/pet-css.test.ts +14 -0
  39. package/src/client/pet-store.test.ts +1 -1
  40. package/src/client/pet.module.css +7 -4
  41. package/src/index.ts +2 -1
  42. package/src/persist.test.ts +53 -1
  43. package/src/persist.ts +46 -0
  44. package/src/service.ts +7 -1
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # node scripts/verify-docs.mjs --write <dir>
5
- README.md: 8b92fda99dc04c8f95ff2e42bf39b42a6a4c1f6f
6
- README.zh.md: d684b6381a9515c6bc7cef55f418ab4e3ab7ee0c
5
+ README.md: 68f173db39a8249a16dcfda3970246ce2d9826b0
6
+ README.zh.md: fd930c40b9182dc42145b3d88c46cfbdbfd309bf
package/README.md CHANGED
@@ -198,7 +198,7 @@ A status bubble can carry a small ornament ahead of its text (built-in: the spou
198
198
  - Structure is fail-closed (unknown fields, out-of-range geometry, non-PNG/WebP entries reject with diagnostics); segment content is warn-and-drop. The machine-readable twin lives at contracts/status-decoration-v1.schema.json; the authoritative validator is src/decoration.ts.
199
199
  - Sources: built-in assets/decorations/ plus the user directory $DSH_HOME/pets/decorations/<id>/ (same id overrides the built-in). Assets ride /api/pet/decoration/<id>/<file> with the same containment and allow-lists as pet assets.
200
200
  - Switch: Settings > Pet > Status decoration (on by default). The built-in whale derives from the DeepSeek wordmark (MIT); see THIRD_PARTY_NOTICES.md.
201
- ## Announcement bubbles (pet.announce, dsh-usage linkage)
201
+ ## Announcement bubbles (pet.announce)
202
202
 
203
203
  Host-side sibling plugins can push one structured announcement through the `pet` cordis service (`ctx.pet.announce({ source, kind, title, ... })`); the browser half renders it as a dedicated, specially styled bubble riding the top of the session bubble stack — its own glass style with a tone accent (`ok`/`warn`/`low`), an amount pill for balance or today-spend kinds, a mini meter for plan-percent windows, and the reset instant. The payload is validated into a bounded shape (src/announce.ts): unknown fields drop, text truncates, TTL clamps to 1 s - 2 h (default 10 s; a repeating announcer declares its poll cadence so an always-mode bubble stays continuous, and the ceiling still unmounts a dead source's bubble within one missed refresh), and a malformed announcement is dropped silently. Announcements are in-memory only: the freshest one wins, an expired one stops rendering, and neither pet.json nor the ledger is touched. There is deliberately no HTTP surface — the API is the in-process service, per the cross-plugin collaboration rule.
204
204
 
@@ -292,6 +292,7 @@ global React root (createRoot → document.body) <-- polling 2s -- pet-client (b
292
292
  - **Status source**: the host projects official `turn/start`, `step/start`, `assistant/message`, `tool/call`, `tool/result`, and `turn/end` events plus live `agent/assistant-stream` chunks into waiting/thinking/tool/review/done/failed states. Optional legacy `activity/status` events remain a compatibility input.
293
293
  - **Registry**: the host normalizes every manifest into a full render definition (geometry, per-row frame counts, per-track durations) and serves it over `/api/pet/pets`; the browser half renders any entry from that definition and carries no per-pet code.
294
294
  - **Selection & naming**: `petId` lives in the settings namespace; per-pet names live in `pet.json` under `names`, edited through the hover-panel rename of the active pet. Legacy installs migrate their flat `name` onto the whale girl.
295
+ - **Display**: `visible`, `size`, `right`, `bottom`, and `bubbleScale` persist in `pet.json` and are edited from the settings card. Bubble typography follows the sprite's own size (12px at the default 160px pet) and `bubbleScale` multiplies that result, bounded to 10–24px so a shrunk pet never carries unreadable text (issue #1549).
295
296
  - **Multi-session semantics**: the API and browser mount are host-global and expose no foreground-session identity. Concurrent sessions each keep their own projected state: the most recent meaningful event drives the sprite animation, while every active TOP-LEVEL session reports its stage in its own bubble (the state view's sessions list, capped at 12 most-recent). Subagent children are tracked for animation, rewards, and the single display bubble but render no bubble of their own, so N conversations never multiply into an N-plus-subagents stack. Every session's completed turns are still rewarded independently; disposing a session removes its bubble, and disposing the display session falls back to the most recent remaining one.
296
297
  - **Mount point**: `document.body` (global React root, always shown: no session / new session / mid-session — the old mount point `conversation.composer.dock` only rendered in an active session, hiding the pet in new sessions); the component uses `createPortal` internally to render the global floating layer. The root follows the plugin fiber lifecycle: fiber disposal unmounts the React root, removes the container, and stops the poll loop and settings subscription; a hot-reloaded or re-injected bundle takes over the page-global slot, so the page always holds exactly one `[data-dsh-pet-root]` (issue #785).
297
298
  - **Rendering**: CSS sprite (background-position) per-frame animation; frame durations and optional scene sequences come from the served definition. The hover panel is anchored below the pet with a pointer bridge across the gap; when the viewport leaves no room below, it flips above the pet and is lifted clear of the status bubble stack so the two never overlap.
package/README.zh.md CHANGED
@@ -198,7 +198,7 @@ frames2d 宠物可声明 `gameplay` 块——从 miku 桌宠泛化而来的可
198
198
  - 结构 fail-closed(未知字段、越界尺寸、非 PNG/WebP 入口直接拒载并进诊断),帧段内容 warn-and-drop。机器可读 schema 见 contracts/status-decoration-v1.schema.json,权威校验器为 src/decoration.ts。
199
199
  - 来源:内置 assets/decorations/ + 用户目录 $DSH_HOME/pets/decorations/<id>/(同 id 覆盖内置)。资产经 /api/pet/decoration/<id>/<file> 路由,containment 与白名单与宠物资产同构。
200
200
  - 开关:设置 → 宠物 → 状态装饰(默认开)。内置鲸鱼素材派生自 DeepSeek wordmark(MIT),完整声明见 THIRD_PARTY_NOTICES.md。
201
- ## 公告气泡(pet.announce,dsh-usage 联动)
201
+ ## 公告气泡(pet.announce)
202
202
 
203
203
  宿主侧的兄弟插件可通过 `pet` cordis 服务推送一条结构化公告(`ctx.pet.announce({ source, kind, title, ... })`);客户端半区将其渲染为一只专用、特别设计的气泡,挂在会话气泡栈顶部——独立玻璃样式与色调描边(`ok`/`warn`/`low`)、余额与今日消费类金额胶囊、套餐百分比窗口带微型计量条、展示重置时间。载荷经有界校验(src/announce.ts):未知字段丢弃、超长文本截断、TTL 收敛到 1 秒 - 2 小时(默认 10 秒;周期型发布方按自身轮询节奏声明 TTL,常驻气泡因此跨轮询连续,上限仍保证失活来源的气泡至多一个刷新周期内消失),畸形公告静默丢弃。公告仅存内存:最新一条生效,过期即不再渲染,不触碰 pet.json 与台账。刻意不提供 HTTP 面——按跨插件协作规则,API 就是进程内服务。
204
204
 
@@ -292,6 +292,7 @@ dsh-pet/
292
292
  - **状态来源**:宿主把官方 `turn/start`、`step/start`、`assistant/message`、`tool/call`、`tool/result`、`turn/end` 事件与实时 `agent/assistant-stream` 增量投影为 waiting/thinking/tool/review/done/failed 状态。可选兼容 `activity/status` 事件仍作为输入。
293
293
  - **注册表**:宿主把每份 manifest 归一化为完整渲染定义(几何、每行帧数、每轨时长),经 `/api/pet/pets` 下发;浏览器半区用该定义渲染任意条目,不携带任何宠物专属代码。
294
294
  - **选择与命名**:`petId` 存于设置命名空间;每只宠物的名字存于 `pet.json` 的 `names`,通过悬浮面板对当前宠物改名编辑。旧版安装的平铺 `name` 自动迁移到鲸鱼娘名下。
295
+ - **显示**:`visible`、`size`、`right`、`bottom` 与 `bubbleScale` 存在 `pet.json`,在设置卡片里编辑。气泡字号跟随精灵自身的尺寸(默认 160px 宠物对应 12px),`bubbleScale` 在此结果上再乘一个倍率,并限制在 10–24px——缩小的宠物不会顶着读不清的字(issue #1549)。
295
296
  - **多会话语义**:API 与浏览器挂载都是宿主全局的,不暴露前台会话身份。并行会话各自保留投影状态:最近一次有意义事件驱动精灵动画,同时每个活动的顶层会话在独立气泡里报告自己的阶段(state 视图的 sessions 列表,最多保留最近 12 个)。子代理会话仍参与动画、计奖与单一显示气泡,但不占独立气泡位——N 个对话不会变成"N + 子代理数"的气泡堆。每个会话完成的轮次仍独立计奖;销毁会话移除它的气泡,销毁当前显示会话则回退到最近仍在活动的会话。
296
297
  - **挂载点**:`document.body`(全局 React 根,始终显示:无会话 / 新会话 / 会话中都可见——旧挂载点 `conversation.composer.dock` 只在活动会话里渲染,新会话里宠物消失);组件内部用 `createPortal` 渲染全局浮层。根容器随插件 fiber 生命周期走:fiber 销毁时卸载 React 根、移除容器并停止轮询与设置订阅;热重载或重复注入的新 bundle 接管页面级单挂载槽,页面始终只有一个 `[data-dsh-pet-root]`(issue #785)。
297
298
  - **渲染**:CSS 精灵(background-position)逐帧动画;帧时长和可选场景序列来自下发定义。悬浮面板锚定在宠物下方,间隙由指针桥接覆盖;当视口下方空间不足时,面板翻转到宠物上方并抬升到状态气泡栈之上,两者互不遮挡。
package/lib/client.js CHANGED
@@ -138,6 +138,24 @@ window.__ModuleLoader__.load({
138
138
  for (var e, t, f = 0, n = "", o = arguments.length; f < o; f++) (e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t);
139
139
  return n;
140
140
  }
141
+ /**
142
+ * Bubble typography scale for one display config (issue #1549): the bubble
143
+ * follows the pet's own size so a shrunk pet does not carry a full-size
144
+ * bubble, and the user's multiplier rides on top. The result is a CSS ratio
145
+ * against {@link BUBBLE_BASE_FONT_PX}, bounded so the text never drops below
146
+ * the readability floor or outgrows the pet. `bubbleScale` is optional at
147
+ * runtime: a host that predates the field (a rolling update, or any snapshot
148
+ * that omits it) falls back to the baseline, because a NaN ratio written into
149
+ * `--pet-bubble-scale` collapses every bubble's text to zero.
150
+ * @param display - display config; `bubbleScale` may be absent on older hosts.
151
+ * @returns the ratio written to `--pet-bubble-scale` (always finite).
152
+ */
153
+ function bubbleScaleFor(display) {
154
+ const size = Number.isFinite(display.size) ? display.size : 160;
155
+ const multiplier = typeof display.bubbleScale === "number" && Number.isFinite(display.bubbleScale) ? display.bubbleScale : 1;
156
+ const scaled = size / 160 * multiplier;
157
+ return Math.round(Math.min(24 / 12, Math.max(10 / 12, scaled)) * 100) / 100;
158
+ }
141
159
  //#endregion
142
160
  //#region src/announce.ts
143
161
  /** Whether an announcement is still fresh at `now`. */
@@ -250,7 +268,7 @@ window.__ModuleLoader__.load({
250
268
  }
251
269
  //#endregion
252
270
  //#region \0dsh-css:packages/dsh-pet/src/client/pet.module.css.mjs
253
- const css$2 = ".kz2Bea_float{pointer-events:auto;-webkit-user-select:none;user-select:none;will-change:transform;contain:layout style;flex-direction:column;align-items:center;display:flex;position:fixed}.kz2Bea_sprite{image-rendering:auto;touch-action:none;contain:paint}.kz2Bea_spriteWrap{flex:none;position:relative}.kz2Bea_bubble{white-space:nowrap;color:#f4f7ff;pointer-events:none;border-radius:999px;margin-bottom:6px;padding:4px 10px;font-size:12px;line-height:1.4;animation:2.6s ease-out forwards kz2Bea_pet-bubble-pop;position:absolute;bottom:100%;box-shadow:0 2px 8px #00000040}.kz2Bea_bubblePet{background:#4d6bfef2}.kz2Bea_bubbleFeed{background:#0369a1f2}.kz2Bea_bubbleStatus{text-overflow:ellipsis;backdrop-filter:blur(8px);letter-spacing:.02em;background:linear-gradient(160deg,#131c36e6,#070b1af0);border:1px solid #7e98ff73;max-width:min(280px,100vw - 24px);padding:5px 12px;animation:.24s ease-out kz2Bea_pet-bubble-in;overflow:hidden;box-shadow:0 4px 12px #02061759,inset 0 1px #e2e8ff1a,0 0 12px #4d6bfe2e}.kz2Bea_bubbleWhisper{letter-spacing:.03em;animation:.32s cubic-bezier(.22,1,.36,1) kz2Bea_pet-whisper-in}.kz2Bea_bubbleWhisper:before{content:\"「\";opacity:.7;margin-right:2px}.kz2Bea_bubbleWhisper:after{content:\"」\";opacity:.7;margin-left:2px}.kz2Bea_bubbleStack{pointer-events:auto;flex-direction:column-reverse;align-items:center;gap:4px;display:flex;position:absolute;bottom:100%}.kz2Bea_bubbleAnchor{display:inline-flex;position:relative}.kz2Bea_bubbleMore{z-index:1;color:#eef2ff;cursor:pointer;background:linear-gradient(160deg,#4a68f5f2,#2f44b8f2);border:1px solid #7e98ff8c;border-radius:999px;justify-content:center;align-items:center;min-width:18px;height:18px;padding:0 5px;font-size:10px;font-weight:600;line-height:1;transition:filter .12s;display:inline-flex;position:absolute;top:-9px;right:-10px;box-shadow:0 2px 6px #02061766}.kz2Bea_bubbleMore:hover{filter:brightness(1.15)}.kz2Bea_bubbleMore:focus-visible{outline:none;box-shadow:0 0 0 2px #7e98ffe6}.kz2Bea_bubbleStack .kz2Bea_bubble{pointer-events:auto;margin-bottom:0;position:relative;bottom:auto}.kz2Bea_bubbleClickable{cursor:pointer;font:inherit;text-align:center;appearance:none;transition:filter .12s,box-shadow .12s}.kz2Bea_bubbleClickable:hover{filter:brightness(1.12)}.kz2Bea_bubbleClickable:focus-visible{outline:none;box-shadow:0 0 0 2px #7e98ffe6}@keyframes kz2Bea_pet-bubble-in{0%{opacity:0;transform:translateY(6px)scale(.96)}to{opacity:1;transform:translateY(0)scale(1)}}@keyframes kz2Bea_pet-whisper-in{0%{opacity:0;transform:translateY(8px)scale(.9)}60%{transform:translateY(-1px)scale(1.02)}to{opacity:1;transform:translateY(0)scale(1)}}@keyframes kz2Bea_pet-panel-in{0%{opacity:0;transform:translateY(4px)scale(.97)}to{opacity:1;transform:translateY(0)scale(1)}}@keyframes kz2Bea_pet-bubble-pop{0%{opacity:0;transform:translateY(6px)scale(.85)}15%{opacity:1;transform:translateY(0)scale(1.05)}25%{transform:translateY(0)scale(1)}75%{opacity:1}to{opacity:0;transform:translateY(-8px)scale(.95)}}.kz2Bea_panel{color:#e6ebf8;backdrop-filter:blur(10px);transform-origin:50% 0;background:linear-gradient(165deg,#131c36f2,#070b1af7);border:1px solid #7e98ff4d;border-radius:12px;flex-direction:column;gap:7px;min-width:148px;margin-top:8px;padding:10px 12px;font-size:12px;animation:.2s cubic-bezier(.22,1,.36,1) kz2Bea_pet-panel-in;display:flex;position:absolute;top:100%;box-shadow:0 8px 24px #02061773,0 0 0 1px #4d6bfe1a,inset 0 1px #e2e8ff14}.kz2Bea_panel:after{content:\"\";height:14px;position:absolute;bottom:100%;left:0;right:0}.kz2Bea_panelAbove{transform-origin:50% 100%;margin-top:0;margin-bottom:8px;top:auto;bottom:100%}.kz2Bea_panelAbove:after{top:100%;bottom:auto}.kz2Bea_rankRow{white-space:nowrap;justify-content:space-between;gap:10px;display:flex}.kz2Bea_nameCell{letter-spacing:.02em;background:linear-gradient(90deg,#a9c1ff,#6c8bff 60%,#4d6bfe);color:#0000;-webkit-background-clip:text;background-clip:text;font-weight:600}.kz2Bea_statRank{color:#9db4ff;font-weight:600}.kz2Bea_statTreats{color:#7dd3fc;font-weight:600}.kz2Bea_statPoints{color:#fcd34d;font-weight:600}.kz2Bea_renameRow{align-items:center;gap:6px;display:flex}.kz2Bea_nameInput{color:#e6ebf8;background:#131c36e6;border:1px solid #7e98ff80;border-radius:6px;outline:none;flex:1;min-width:0;padding:3px 6px;font-size:12px}.kz2Bea_nameInput:focus{border-color:#4d6bfe;box-shadow:0 0 0 2px #4d6bfe73}.kz2Bea_actions{gap:6px;display:flex}.kz2Bea_action{cursor:pointer;color:#fff;background:linear-gradient(#4a68f5,#3a55e0);border:none;border-radius:6px;flex:1;padding:4px 8px;font-size:12px;font-weight:600;transition:filter .12s,box-shadow .12s,transform .12s;box-shadow:0 2px 6px #4d6bfe4d}.kz2Bea_action:hover{filter:brightness(1.08);transform:translateY(-1px);box-shadow:0 4px 10px #4d6bfe66}.kz2Bea_action:active{filter:brightness(.94);transform:translateY(0);box-shadow:0 1px 4px #4d6bfe4d}.kz2Bea_action:focus-visible{outline:none;box-shadow:0 0 0 2px #7e98ffe6}.kz2Bea_summon{color:#8ea6ff;cursor:pointer;background:#070b1abf;border:1px dashed #7e98ff99;border-radius:999px;padding:2px 10px;font-size:11px;transition:border-color .12s,color .12s,background .12s,box-shadow .12s}.kz2Bea_summon:hover{color:#c3d3ff;background:#070b1ae6;border-color:#7e98fff2}.kz2Bea_summon:active{color:#8ea6ff;border-color:#7e98ffcc}.kz2Bea_summon:focus-visible{outline:none;box-shadow:0 0 0 2px #7e98ffe6}@media (prefers-reduced-motion:reduce){.kz2Bea_bubble,.kz2Bea_bubbleStatus,.kz2Bea_bubbleWhisper,.kz2Bea_panel{opacity:1;animation:none}.kz2Bea_action,.kz2Bea_summon,.kz2Bea_bubbleMore{transition:none}}.kz2Bea_gameplayHud{width:0;height:0;position:absolute;bottom:0;left:0}.kz2Bea_gameplayModeChip{color:#cdd7ff;white-space:nowrap;pointer-events:none;background:#7e98ff2e;border-radius:999px;padding:1px 8px;font-size:10px;position:absolute;top:0;left:0;transform:translateY(-100%)translateY(-4px)}.kz2Bea_gameplayCard{color:#dfe6ff;background:#070b1aeb;border:1px solid #7e98ff40;border-radius:10px;min-width:180px;max-height:340px;padding:10px 12px;font-size:12px;position:absolute;bottom:0;left:0;overflow-y:auto;transform:none;box-shadow:0 8px 24px #0006}.kz2Bea_gameplayBars{flex-direction:column;gap:4px;margin-bottom:8px;display:flex}.kz2Bea_gameplayBarRow{align-items:center;gap:6px;display:flex}.kz2Bea_gameplayBarLabel{color:#9aa8d8;flex-shrink:0;width:34px}.kz2Bea_gameplayBarTrack{background:#7e98ff26;border-radius:3px;flex:1;height:6px;overflow:hidden}.kz2Bea_gameplayBarFill{background:linear-gradient(90deg,#7e98ff,#9db4ff);border-radius:3px;height:100%;transition:width .3s;display:block}.kz2Bea_gameplayActions{flex-wrap:wrap;gap:6px;display:flex}.kz2Bea_gameplayShopItems{grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin-bottom:8px;display:grid}.kz2Bea_gameplayShopItem{color:inherit;cursor:pointer;background:#7e98ff14;border:1px solid #7e98ff40;border-radius:8px;flex-direction:column;align-items:center;gap:4px;padding:8px 6px;display:flex}.kz2Bea_gameplayShopItem:hover{background:#7e98ff29;border-color:#7e98ffb3}.kz2Bea_gameplayShopItemImage{object-fit:contain;width:36px;height:36px;image-rendering:pixelated;pointer-events:none}.kz2Bea_gameplayShopItemLabel{text-align:center;font-size:11px}.kz2Bea_gameplayShopItemPrice{color:#ffd27e;font-size:10px}.kz2Bea_gameplaySkinItems{flex-direction:column;gap:6px;margin-bottom:8px;display:flex}.kz2Bea_gameplaySkinItem{width:100%;color:inherit;cursor:pointer;text-align:left;background:#7e98ff14;border:1px solid #7e98ff40;border-radius:8px;padding:8px 10px;font-size:12px;display:block}.kz2Bea_gameplaySkinItem:hover{background:#7e98ff29;border-color:#7e98ffb3}.kz2Bea_gameplaySkinItemActive{background:#7e98ff47;border-color:#7e98ffe6}.kz2Bea_gameplayClose{color:#9aa8d8;cursor:pointer;background:0 0;border:none;padding:0 2px;font-size:14px;position:absolute;top:4px;right:8px}.kz2Bea_gameplayClose:hover{color:#fff}.kz2Bea_gameplayFloat{color:#3a2a00;white-space:nowrap;pointer-events:none;background:#ffd27eeb;border-radius:999px;padding:2px 10px;font-size:11px;animation:1.1s ease-out forwards kz2Bea_pet-gameplay-float;position:absolute;top:-8px;left:50%;transform:translate(-50%)}@keyframes kz2Bea_pet-gameplay-float{0%{opacity:0;transform:translate(-50%)translateY(6px)}20%{opacity:1}to{opacity:0;transform:translate(-50%)translateY(-18px)}}@media (prefers-reduced-motion:reduce){.kz2Bea_gameplayFloat{opacity:1;animation:none}.kz2Bea_gameplayBarFill{transition:none}}.kz2Bea_bubbleUsage{white-space:nowrap;backdrop-filter:blur(8px);letter-spacing:.02em;background:linear-gradient(160deg,#131c36eb,#070b1af2);border:1px solid #7e98ff73;flex-direction:column;align-items:stretch;gap:3px;min-width:128px;max-width:min(280px,100vw - 24px);padding:6px 12px;animation:.28s cubic-bezier(.22,1,.36,1) kz2Bea_pet-usage-in;display:flex;box-shadow:0 4px 12px #02061759,inset 0 1px #e2e8ff1a,0 0 12px #4d6bfe2e}.kz2Bea_bubbleUsageHead{justify-content:space-between;align-items:baseline;gap:10px;display:flex}.kz2Bea_bubbleUsageTitle{text-overflow:ellipsis;opacity:.85;overflow:hidden}.kz2Bea_bubbleUsageValue{font-variant-numeric:tabular-nums;flex-shrink:0;font-weight:600}.kz2Bea_bubbleUsageNote{text-overflow:ellipsis;opacity:.55;font-size:10px;overflow:hidden}.kz2Bea_bubbleUsageMeter{background:#e2e8ff24;border-radius:999px;height:4px;display:block;overflow:hidden}.kz2Bea_bubbleUsageMeterFill{background:#6ee7b7;border-radius:999px;height:100%;transition:width .3s;display:block}.kz2Bea_bubbleUsageOk{border-color:#7e98ff73}.kz2Bea_bubbleUsageWarn{border-color:#fbbf248c}.kz2Bea_bubbleUsageWarn .kz2Bea_bubbleUsageMeterFill{background:#fbbf24}.kz2Bea_bubbleUsageLow{border-color:#f8717199}.kz2Bea_bubbleUsageLow .kz2Bea_bubbleUsageMeterFill{background:#f87171}@keyframes kz2Bea_pet-usage-in{0%{opacity:0;transform:translateY(6px)scale(.96)}to{opacity:1;transform:translateY(0)scale(1)}}@media (prefers-reduced-motion:reduce){.kz2Bea_bubbleUsage{animation:none}}";
271
+ const css$2 = ".kz2Bea_float{pointer-events:auto;-webkit-user-select:none;user-select:none;will-change:transform;contain:layout style;flex-direction:column;align-items:center;display:flex;position:fixed}.kz2Bea_sprite{image-rendering:auto;touch-action:none;contain:paint}.kz2Bea_spriteWrap{flex:none;position:relative}.kz2Bea_bubble{white-space:nowrap;padding:calc(4px * var(--pet-bubble-scale,1)) calc(10px * var(--pet-bubble-scale,1));font-size:calc(12px * var(--pet-bubble-scale,1));color:#f4f7ff;pointer-events:none;border-radius:999px;margin-bottom:6px;line-height:1.4;animation:2.6s ease-out forwards kz2Bea_pet-bubble-pop;position:absolute;bottom:100%;box-shadow:0 2px 8px #00000040}.kz2Bea_bubblePet{background:#4d6bfef2}.kz2Bea_bubbleFeed{background:#0369a1f2}.kz2Bea_bubbleStatus{max-width:min(calc(280px * var(--pet-bubble-scale,1)), calc(100vw - 24px));text-overflow:ellipsis;padding:calc(5px * var(--pet-bubble-scale,1)) calc(12px * var(--pet-bubble-scale,1));backdrop-filter:blur(8px);letter-spacing:.02em;background:linear-gradient(160deg,#131c36e6,#070b1af0);border:1px solid #7e98ff73;animation:.24s ease-out kz2Bea_pet-bubble-in;overflow:hidden;box-shadow:0 4px 12px #02061759,inset 0 1px #e2e8ff1a,0 0 12px #4d6bfe2e}.kz2Bea_bubbleWhisper{letter-spacing:.03em;animation:.32s cubic-bezier(.22,1,.36,1) kz2Bea_pet-whisper-in}.kz2Bea_bubbleWhisper:before{content:\"「\";opacity:.7;margin-right:2px}.kz2Bea_bubbleWhisper:after{content:\"」\";opacity:.7;margin-left:2px}.kz2Bea_bubbleStack{pointer-events:auto;flex-direction:column-reverse;align-items:center;gap:4px;display:flex;position:absolute;bottom:100%}.kz2Bea_bubbleAnchor{display:inline-flex;position:relative}.kz2Bea_bubbleMore{z-index:1;color:#eef2ff;cursor:pointer;background:linear-gradient(160deg,#4a68f5f2,#2f44b8f2);border:1px solid #7e98ff8c;border-radius:999px;justify-content:center;align-items:center;min-width:18px;height:18px;padding:0 5px;font-size:10px;font-weight:600;line-height:1;transition:filter .12s;display:inline-flex;position:absolute;top:-9px;right:-10px;box-shadow:0 2px 6px #02061766}.kz2Bea_bubbleMore:hover{filter:brightness(1.15)}.kz2Bea_bubbleMore:focus-visible{outline:none;box-shadow:0 0 0 2px #7e98ffe6}.kz2Bea_bubbleStack .kz2Bea_bubble{pointer-events:auto;margin-bottom:0;position:relative;bottom:auto}.kz2Bea_bubbleClickable{cursor:pointer;font:inherit;text-align:center;appearance:none;transition:filter .12s,box-shadow .12s}.kz2Bea_bubbleClickable:hover{filter:brightness(1.12)}.kz2Bea_bubbleClickable:focus-visible{outline:none;box-shadow:0 0 0 2px #7e98ffe6}@keyframes kz2Bea_pet-bubble-in{0%{opacity:0;transform:translateY(6px)scale(.96)}to{opacity:1;transform:translateY(0)scale(1)}}@keyframes kz2Bea_pet-whisper-in{0%{opacity:0;transform:translateY(8px)scale(.9)}60%{transform:translateY(-1px)scale(1.02)}to{opacity:1;transform:translateY(0)scale(1)}}@keyframes kz2Bea_pet-panel-in{0%{opacity:0;transform:translateY(4px)scale(.97)}to{opacity:1;transform:translateY(0)scale(1)}}@keyframes kz2Bea_pet-bubble-pop{0%{opacity:0;transform:translateY(6px)scale(.85)}15%{opacity:1;transform:translateY(0)scale(1.05)}25%{transform:translateY(0)scale(1)}75%{opacity:1}to{opacity:0;transform:translateY(-8px)scale(.95)}}.kz2Bea_panel{color:#e6ebf8;backdrop-filter:blur(10px);transform-origin:50% 0;background:linear-gradient(165deg,#131c36f2,#070b1af7);border:1px solid #7e98ff4d;border-radius:12px;flex-direction:column;gap:7px;min-width:148px;margin-top:8px;padding:10px 12px;font-size:12px;animation:.2s cubic-bezier(.22,1,.36,1) kz2Bea_pet-panel-in;display:flex;position:absolute;top:100%;box-shadow:0 8px 24px #02061773,0 0 0 1px #4d6bfe1a,inset 0 1px #e2e8ff14}.kz2Bea_panel:after{content:\"\";height:14px;position:absolute;bottom:100%;left:0;right:0}.kz2Bea_panelAbove{transform-origin:50% 100%;margin-top:0;margin-bottom:8px;top:auto;bottom:100%}.kz2Bea_panelAbove:after{top:100%;bottom:auto}.kz2Bea_rankRow{white-space:nowrap;justify-content:space-between;gap:10px;display:flex}.kz2Bea_nameCell{letter-spacing:.02em;background:linear-gradient(90deg,#a9c1ff,#6c8bff 60%,#4d6bfe);color:#0000;-webkit-background-clip:text;background-clip:text;font-weight:600}.kz2Bea_statRank{color:#9db4ff;font-weight:600}.kz2Bea_statTreats{color:#7dd3fc;font-weight:600}.kz2Bea_statPoints{color:#fcd34d;font-weight:600}.kz2Bea_renameRow{align-items:center;gap:6px;display:flex}.kz2Bea_nameInput{color:#e6ebf8;background:#131c36e6;border:1px solid #7e98ff80;border-radius:6px;outline:none;flex:1;min-width:0;padding:3px 6px;font-size:12px}.kz2Bea_nameInput:focus{border-color:#4d6bfe;box-shadow:0 0 0 2px #4d6bfe73}.kz2Bea_actions{gap:6px;display:flex}.kz2Bea_action{cursor:pointer;color:#fff;background:linear-gradient(#4a68f5,#3a55e0);border:none;border-radius:6px;flex:1;padding:4px 8px;font-size:12px;font-weight:600;transition:filter .12s,box-shadow .12s,transform .12s;box-shadow:0 2px 6px #4d6bfe4d}.kz2Bea_action:hover{filter:brightness(1.08);transform:translateY(-1px);box-shadow:0 4px 10px #4d6bfe66}.kz2Bea_action:active{filter:brightness(.94);transform:translateY(0);box-shadow:0 1px 4px #4d6bfe4d}.kz2Bea_action:focus-visible{outline:none;box-shadow:0 0 0 2px #7e98ffe6}.kz2Bea_summon{color:#8ea6ff;cursor:pointer;background:#070b1abf;border:1px dashed #7e98ff99;border-radius:999px;padding:2px 10px;font-size:11px;transition:border-color .12s,color .12s,background .12s,box-shadow .12s}.kz2Bea_summon:hover{color:#c3d3ff;background:#070b1ae6;border-color:#7e98fff2}.kz2Bea_summon:active{color:#8ea6ff;border-color:#7e98ffcc}.kz2Bea_summon:focus-visible{outline:none;box-shadow:0 0 0 2px #7e98ffe6}@media (prefers-reduced-motion:reduce){.kz2Bea_bubble,.kz2Bea_bubbleStatus,.kz2Bea_bubbleWhisper,.kz2Bea_panel{opacity:1;animation:none}.kz2Bea_action,.kz2Bea_summon,.kz2Bea_bubbleMore{transition:none}}.kz2Bea_gameplayHud{width:0;height:0;position:absolute;bottom:0;left:0}.kz2Bea_gameplayModeChip{color:#cdd7ff;white-space:nowrap;pointer-events:none;background:#7e98ff2e;border-radius:999px;padding:1px 8px;font-size:10px;position:absolute;top:0;left:0;transform:translateY(-100%)translateY(-4px)}.kz2Bea_gameplayCard{color:#dfe6ff;background:#070b1aeb;border:1px solid #7e98ff40;border-radius:10px;min-width:180px;max-height:340px;padding:10px 12px;font-size:12px;position:absolute;bottom:0;left:0;overflow-y:auto;transform:none;box-shadow:0 8px 24px #0006}.kz2Bea_gameplayBars{flex-direction:column;gap:4px;margin-bottom:8px;display:flex}.kz2Bea_gameplayBarRow{align-items:center;gap:6px;display:flex}.kz2Bea_gameplayBarLabel{color:#9aa8d8;flex-shrink:0;width:34px}.kz2Bea_gameplayBarTrack{background:#7e98ff26;border-radius:3px;flex:1;height:6px;overflow:hidden}.kz2Bea_gameplayBarFill{background:linear-gradient(90deg,#7e98ff,#9db4ff);border-radius:3px;height:100%;transition:width .3s;display:block}.kz2Bea_gameplayActions{flex-wrap:wrap;gap:6px;display:flex}.kz2Bea_gameplayShopItems{grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin-bottom:8px;display:grid}.kz2Bea_gameplayShopItem{color:inherit;cursor:pointer;background:#7e98ff14;border:1px solid #7e98ff40;border-radius:8px;flex-direction:column;align-items:center;gap:4px;padding:8px 6px;display:flex}.kz2Bea_gameplayShopItem:hover{background:#7e98ff29;border-color:#7e98ffb3}.kz2Bea_gameplayShopItemImage{object-fit:contain;width:36px;height:36px;image-rendering:pixelated;pointer-events:none}.kz2Bea_gameplayShopItemLabel{text-align:center;font-size:11px}.kz2Bea_gameplayShopItemPrice{color:#ffd27e;font-size:10px}.kz2Bea_gameplaySkinItems{flex-direction:column;gap:6px;margin-bottom:8px;display:flex}.kz2Bea_gameplaySkinItem{width:100%;color:inherit;cursor:pointer;text-align:left;background:#7e98ff14;border:1px solid #7e98ff40;border-radius:8px;padding:8px 10px;font-size:12px;display:block}.kz2Bea_gameplaySkinItem:hover{background:#7e98ff29;border-color:#7e98ffb3}.kz2Bea_gameplaySkinItemActive{background:#7e98ff47;border-color:#7e98ffe6}.kz2Bea_gameplayClose{color:#9aa8d8;cursor:pointer;background:0 0;border:none;padding:0 2px;font-size:14px;position:absolute;top:4px;right:8px}.kz2Bea_gameplayClose:hover{color:#fff}.kz2Bea_gameplayFloat{color:#3a2a00;white-space:nowrap;pointer-events:none;background:#ffd27eeb;border-radius:999px;padding:2px 10px;font-size:11px;animation:1.1s ease-out forwards kz2Bea_pet-gameplay-float;position:absolute;top:-8px;left:50%;transform:translate(-50%)}@keyframes kz2Bea_pet-gameplay-float{0%{opacity:0;transform:translate(-50%)translateY(6px)}20%{opacity:1}to{opacity:0;transform:translate(-50%)translateY(-18px)}}@media (prefers-reduced-motion:reduce){.kz2Bea_gameplayFloat{opacity:1;animation:none}.kz2Bea_gameplayBarFill{transition:none}}.kz2Bea_bubbleUsage{white-space:nowrap;backdrop-filter:blur(8px);letter-spacing:.02em;background:linear-gradient(160deg,#131c36eb,#070b1af2);border:1px solid #7e98ff73;flex-direction:column;align-items:stretch;gap:3px;min-width:128px;max-width:min(280px,100vw - 24px);padding:6px 12px;animation:.28s cubic-bezier(.22,1,.36,1) kz2Bea_pet-usage-in;display:flex;box-shadow:0 4px 12px #02061759,inset 0 1px #e2e8ff1a,0 0 12px #4d6bfe2e}.kz2Bea_bubbleUsageHead{justify-content:space-between;align-items:baseline;gap:10px;display:flex}.kz2Bea_bubbleUsageTitle{text-overflow:ellipsis;opacity:.85;overflow:hidden}.kz2Bea_bubbleUsageValue{font-variant-numeric:tabular-nums;flex-shrink:0;font-weight:600}.kz2Bea_bubbleUsageNote{text-overflow:ellipsis;opacity:.55;font-size:10px;overflow:hidden}.kz2Bea_bubbleUsageMeter{background:#e2e8ff24;border-radius:999px;height:4px;display:block;overflow:hidden}.kz2Bea_bubbleUsageMeterFill{background:#6ee7b7;border-radius:999px;height:100%;transition:width .3s;display:block}.kz2Bea_bubbleUsageOk{border-color:#7e98ff73}.kz2Bea_bubbleUsageWarn{border-color:#fbbf248c}.kz2Bea_bubbleUsageWarn .kz2Bea_bubbleUsageMeterFill{background:#fbbf24}.kz2Bea_bubbleUsageLow{border-color:#f8717199}.kz2Bea_bubbleUsageLow .kz2Bea_bubbleUsageMeterFill{background:#f87171}@keyframes kz2Bea_pet-usage-in{0%{opacity:0;transform:translateY(6px)scale(.96)}to{opacity:1;transform:translateY(0)scale(1)}}@media (prefers-reduced-motion:reduce){.kz2Bea_bubbleUsage{animation:none}}";
254
272
  const tagId$2 = "@linxin666/dsh-pet/packages/dsh-pet/src/client/pet.module.css";
255
273
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
256
274
  const tag = document.createElement("style");
@@ -511,15 +529,36 @@ window.__ModuleLoader__.load({
511
529
  const panelShows = (action) => panel?.actions === void 0 || panel.actions.includes(action);
512
530
  (0, react.useEffect)(() => {
513
531
  if (props.visual !== void 0) return;
532
+ setImageReady(false);
514
533
  let cancelled = false;
515
- const img = new Image();
516
- img.onload = () => {
517
- if (!cancelled) setImageReady(true);
534
+ let retryTimer;
535
+ let attempt = 0;
536
+ const maxAttempts = 3;
537
+ let activeImg = null;
538
+ const loadAtlas = () => {
539
+ const img = new Image();
540
+ activeImg = img;
541
+ img.onload = () => {
542
+ if (!cancelled) setImageReady(true);
543
+ };
544
+ img.onerror = () => {
545
+ if (cancelled) return;
546
+ if (attempt < maxAttempts) {
547
+ attempt += 1;
548
+ const delay = Math.min(1e3 * Math.pow(2, attempt - 1), 8e3);
549
+ retryTimer = setTimeout(loadAtlas, delay);
550
+ }
551
+ };
552
+ img.src = definition.atlasUrl;
518
553
  };
519
- img.src = definition.atlasUrl;
554
+ loadAtlas();
520
555
  return () => {
521
556
  cancelled = true;
522
- img.onload = null;
557
+ if (retryTimer !== void 0) clearTimeout(retryTimer);
558
+ if (activeImg !== null) {
559
+ activeImg.onload = null;
560
+ activeImg.onerror = null;
561
+ }
523
562
  };
524
563
  }, [definition.atlasUrl, props.visual]);
525
564
  const spriteScale = display.size / cell.height;
@@ -663,6 +702,7 @@ window.__ModuleLoader__.load({
663
702
  };
664
703
  const spriteWidth = Math.round(cell.width * spriteScale);
665
704
  const spriteHeight = Math.round(cell.height * spriteScale);
705
+ const bubbleScale = bubbleScaleFor(display);
666
706
  const sessionBubbles = snapshot?.sessions ?? [];
667
707
  const stackOpen = stackPeek || stackPinned;
668
708
  const visibleSessions = !stackOpen && sessionBubbles.length > 1 ? sessionBubbles.slice(0, 1) : sessionBubbles;
@@ -710,7 +750,8 @@ window.__ModuleLoader__.load({
710
750
  style: {
711
751
  right: pos.right,
712
752
  bottom: pos.bottom,
713
- zIndex: 2147483e3
753
+ zIndex: 2147483e3,
754
+ "--pet-bubble-scale": String(bubbleScale)
714
755
  },
715
756
  onPointerEnter: () => {
716
757
  clearHideTimer();
@@ -1704,7 +1745,8 @@ window.__ModuleLoader__.load({
1704
1745
  visible: true,
1705
1746
  size: 160,
1706
1747
  right: 24,
1707
- bottom: 20
1748
+ bottom: 20,
1749
+ bubbleScale: 1
1708
1750
  };
1709
1751
  /**
1710
1752
  * Dock entry: while the pet is visible, mount the floating PetSprite (it
@@ -3318,6 +3360,7 @@ window.__ModuleLoader__.load({
3318
3360
  numberField("size"),
3319
3361
  numberField("right"),
3320
3362
  numberField("bottom"),
3363
+ numberField("bubbleScale"),
3321
3364
  choiceField("petId", this.petChoices)
3322
3365
  ]);
3323
3366
  this.store = this.form.bind(() => this.projection());
@@ -3367,6 +3410,7 @@ window.__ModuleLoader__.load({
3367
3410
  size: this.form.field("size"),
3368
3411
  right: this.form.field("right"),
3369
3412
  bottom: this.form.field("bottom"),
3413
+ bubbleScale: this.form.field("bubbleScale"),
3370
3414
  petId: this.form.field("petId"),
3371
3415
  petChoices: this.petChoices.map((id) => ({
3372
3416
  value: id,
@@ -3538,6 +3582,20 @@ window.__ModuleLoader__.load({
3538
3582
  onReset: () => {
3539
3583
  props.resetField("bottom");
3540
3584
  }
3585
+ }),
3586
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ValueField, {
3587
+ id: "settings-pet-bubble-scale",
3588
+ label: t("settings.bubbleScale"),
3589
+ hint: t("settings.bubbleScaleHint"),
3590
+ numeric: true,
3591
+ ...fieldProps,
3592
+ ...state.bubbleScale,
3593
+ onEdit: (text) => {
3594
+ props.edit("bubbleScale", text);
3595
+ },
3596
+ onReset: () => {
3597
+ props.resetField("bubbleScale");
3598
+ }
3541
3599
  })
3542
3600
  ]
3543
3601
  });
@@ -3620,6 +3678,8 @@ window.__ModuleLoader__.load({
3620
3678
  "settings.visibleHint": "关闭后宠物隐藏,可从聊天输入区重新召唤。",
3621
3679
  "settings.size": "大小(px)",
3622
3680
  "settings.sizeHint": "精灵单元高度,范围 32–1024。",
3681
+ "settings.bubbleScale": "气泡字号倍率",
3682
+ "settings.bubbleScaleHint": "状态、碎碎念与用量气泡的字号会随宠物大小自动缩放;这一项是在此基础上再乘一个倍率(0.5–2,默认 1)。字号本身不小于 10px、不大于 24px。",
3623
3683
  "settings.right": "距右侧(px)",
3624
3684
  "settings.rightHint": "距视口右边缘的水平内缩距离。",
3625
3685
  "settings.bottom": "距底部(px)",
@@ -3701,6 +3761,8 @@ window.__ModuleLoader__.load({
3701
3761
  "settings.visibleHint": "When off, the pet hides; summon it again from the input row.",
3702
3762
  "settings.size": "Size (px)",
3703
3763
  "settings.sizeHint": "Sprite cell height, 32–1024.",
3764
+ "settings.bubbleScale": "Bubble text multiplier",
3765
+ "settings.bubbleScaleHint": "Status, whisper, and usage bubbles scale with the pet automatically; this multiplies that result (0.5–2, default 1). The rendered text stays between 10px and 24px.",
3704
3766
  "settings.right": "Right inset (px)",
3705
3767
  "settings.rightHint": "Horizontal inset from the viewport right edge.",
3706
3768
  "settings.bottom": "Bottom inset (px)",
@@ -3751,7 +3813,7 @@ window.__ModuleLoader__.load({
3751
3813
  /** The building package's version, when the bundle carries it. */
3752
3814
  function bakedVersion() {
3753
3815
  try {
3754
- return "0.3.22";
3816
+ return "0.3.24";
3755
3817
  } catch {
3756
3818
  return;
3757
3819
  }