@linxin666/dsh-pet 0.3.24 → 0.4.2

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 (72) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +4 -3
  3. package/README.zh.md +4 -3
  4. package/icon.svg +3 -0
  5. package/lib/client.js +522 -194
  6. package/lib/client.js.map +1 -1
  7. package/lib/index.js +706 -422
  8. package/lib/live2d-vendor.js +96 -96
  9. package/lib/live2d-vendor.js.map +1 -1
  10. package/lib/types/client/PetDockEntry.d.ts.map +1 -1
  11. package/lib/types/client/PetDockEntry.js +1 -1
  12. package/lib/types/client/PetSettingsCard.d.ts +20 -9
  13. package/lib/types/client/PetSettingsCard.d.ts.map +1 -1
  14. package/lib/types/client/PetSettingsCard.js +116 -5
  15. package/lib/types/client/PetSprite.d.ts +6 -0
  16. package/lib/types/client/PetSprite.d.ts.map +1 -1
  17. package/lib/types/client/PetSprite.js +95 -0
  18. package/lib/types/client/PluginSettingsCard.d.ts +4 -0
  19. package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
  20. package/lib/types/client/PluginSettingsCard.js +3 -2
  21. package/lib/types/client/gameplay-hud.d.ts +10 -1
  22. package/lib/types/client/gameplay-hud.d.ts.map +1 -1
  23. package/lib/types/client/gameplay-hud.js +137 -23
  24. package/lib/types/client/index.d.ts +8 -5
  25. package/lib/types/client/index.d.ts.map +1 -1
  26. package/lib/types/client/index.js +61 -27
  27. package/lib/types/client/main-session.d.ts +33 -0
  28. package/lib/types/client/main-session.d.ts.map +1 -0
  29. package/lib/types/client/main-session.js +34 -0
  30. package/lib/types/client/settings-form.d.ts +17 -17
  31. package/lib/types/client/settings-form.d.ts.map +1 -1
  32. package/lib/types/client/settings-form.js +30 -27
  33. package/lib/types/event-projection.d.ts.map +1 -1
  34. package/lib/types/event-projection.js +5 -3
  35. package/lib/types/gameplay.d.ts +79 -5
  36. package/lib/types/gameplay.d.ts.map +1 -1
  37. package/lib/types/gameplay.js +212 -17
  38. package/lib/types/http.d.ts +18 -1
  39. package/lib/types/http.d.ts.map +1 -1
  40. package/lib/types/http.js +22 -1
  41. package/lib/types/index.d.ts +100 -29
  42. package/lib/types/index.d.ts.map +1 -1
  43. package/lib/types/index.js +113 -64
  44. package/lib/types/persist.d.ts.map +1 -1
  45. package/lib/types/persist.js +3 -1
  46. package/lib/types/routes.d.ts.map +1 -1
  47. package/lib/types/routes.js +3 -1
  48. package/lib/types/service.d.ts +8 -3
  49. package/lib/types/service.d.ts.map +1 -1
  50. package/lib/types/service.js +26 -4
  51. package/package.json +46 -26
  52. package/src/client/PetDockEntry.tsx +1 -0
  53. package/src/client/PetSettingsCard.test.tsx +103 -0
  54. package/src/client/PetSettingsCard.tsx +129 -26
  55. package/src/client/PetSprite.tsx +96 -0
  56. package/src/client/PluginSettingsCard.tsx +7 -2
  57. package/src/client/gameplay-hud.test.tsx +106 -8
  58. package/src/client/gameplay-hud.tsx +143 -28
  59. package/src/client/index.test.tsx +37 -4
  60. package/src/client/index.ts +74 -34
  61. package/src/client/main-session.ts +43 -0
  62. package/src/client/settings-form.ts +34 -31
  63. package/src/event-projection.ts +5 -3
  64. package/src/gameplay.test.ts +138 -1
  65. package/src/gameplay.ts +253 -16
  66. package/src/http.ts +23 -1
  67. package/src/index.test.ts +107 -11
  68. package/src/index.ts +162 -73
  69. package/src/persist.ts +3 -1
  70. package/src/registry.test.ts +3 -2
  71. package/src/routes.ts +3 -1
  72. package/src/service.ts +27 -5
package/lib/client.js CHANGED
@@ -124,7 +124,7 @@ window.__ModuleLoader__.load({
124
124
  };
125
125
  }
126
126
  //#endregion
127
- //#region ../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
127
+ //#region node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
128
128
  function r(e) {
129
129
  var t, f, n = "";
130
130
  if ("string" == typeof e || "number" == typeof e) n += e;
@@ -154,7 +154,7 @@ window.__ModuleLoader__.load({
154
154
  const size = Number.isFinite(display.size) ? display.size : 160;
155
155
  const multiplier = typeof display.bubbleScale === "number" && Number.isFinite(display.bubbleScale) ? display.bubbleScale : 1;
156
156
  const scaled = size / 160 * multiplier;
157
- return Math.round(Math.min(24 / 12, Math.max(10 / 12, scaled)) * 100) / 100;
157
+ return Math.round(Math.min(2, Math.max(10 / 12, scaled)) * 100) / 100;
158
158
  }
159
159
  //#endregion
160
160
  //#region src/announce.ts
@@ -267,9 +267,9 @@ window.__ModuleLoader__.load({
267
267
  } };
268
268
  }
269
269
  //#endregion
270
- //#region \0dsh-css:packages/dsh-pet/src/client/pet.module.css.mjs
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}}";
272
- const tagId$2 = "@linxin666/dsh-pet/packages/dsh-pet/src/client/pet.module.css";
270
+ //#region \0dsh-css:src/client/pet.module.css.mjs
271
+ const css$2 = ".v78Lda_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}.v78Lda_sprite{image-rendering:auto;touch-action:none;contain:paint}.v78Lda_spriteWrap{flex:none;position:relative}.v78Lda_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 v78Lda_pet-bubble-pop;position:absolute;bottom:100%;box-shadow:0 2px 8px #00000040}.v78Lda_bubblePet{background:#4d6bfef2}.v78Lda_bubbleFeed{background:#0369a1f2}.v78Lda_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 v78Lda_pet-bubble-in;overflow:hidden;box-shadow:0 4px 12px #02061759,inset 0 1px #e2e8ff1a,0 0 12px #4d6bfe2e}.v78Lda_bubbleWhisper{letter-spacing:.03em;animation:.32s cubic-bezier(.22,1,.36,1) v78Lda_pet-whisper-in}.v78Lda_bubbleWhisper:before{content:\"「\";opacity:.7;margin-right:2px}.v78Lda_bubbleWhisper:after{content:\"」\";opacity:.7;margin-left:2px}.v78Lda_bubbleStack{pointer-events:auto;flex-direction:column-reverse;align-items:center;gap:4px;display:flex;position:absolute;bottom:100%}.v78Lda_bubbleAnchor{display:inline-flex;position:relative}.v78Lda_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}.v78Lda_bubbleMore:hover{filter:brightness(1.15)}.v78Lda_bubbleMore:focus-visible{outline:none;box-shadow:0 0 0 2px #7e98ffe6}.v78Lda_bubbleStack .v78Lda_bubble{pointer-events:auto;margin-bottom:0;position:relative;bottom:auto}.v78Lda_bubbleClickable{cursor:pointer;font:inherit;text-align:center;appearance:none;transition:filter .12s,box-shadow .12s}.v78Lda_bubbleClickable:hover{filter:brightness(1.12)}.v78Lda_bubbleClickable:focus-visible{outline:none;box-shadow:0 0 0 2px #7e98ffe6}@keyframes v78Lda_pet-bubble-in{0%{opacity:0;transform:translateY(6px)scale(.96)}to{opacity:1;transform:translateY(0)scale(1)}}@keyframes v78Lda_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 v78Lda_pet-panel-in{0%{opacity:0;transform:translateY(4px)scale(.97)}to{opacity:1;transform:translateY(0)scale(1)}}@keyframes v78Lda_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)}}.v78Lda_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) v78Lda_pet-panel-in;display:flex;position:absolute;top:100%;box-shadow:0 8px 24px #02061773,0 0 0 1px #4d6bfe1a,inset 0 1px #e2e8ff14}.v78Lda_panel:after{content:\"\";height:14px;position:absolute;bottom:100%;left:0;right:0}.v78Lda_panelAbove{transform-origin:50% 100%;margin-top:0;margin-bottom:8px;top:auto;bottom:100%}.v78Lda_panelAbove:after{top:100%;bottom:auto}.v78Lda_rankRow{white-space:nowrap;justify-content:space-between;gap:10px;display:flex}.v78Lda_nameCell{letter-spacing:.02em;background:linear-gradient(90deg,#a9c1ff,#6c8bff 60%,#4d6bfe);color:#0000;-webkit-background-clip:text;background-clip:text;font-weight:600}.v78Lda_statRank{color:#9db4ff;font-weight:600}.v78Lda_statTreats{color:#7dd3fc;font-weight:600}.v78Lda_statPoints{color:#fcd34d;font-weight:600}.v78Lda_renameRow{align-items:center;gap:6px;display:flex}.v78Lda_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}.v78Lda_nameInput:focus{border-color:#4d6bfe;box-shadow:0 0 0 2px #4d6bfe73}.v78Lda_actions{gap:6px;display:flex}.v78Lda_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}.v78Lda_action:hover{filter:brightness(1.08);transform:translateY(-1px);box-shadow:0 4px 10px #4d6bfe66}.v78Lda_action:active{filter:brightness(.94);transform:translateY(0);box-shadow:0 1px 4px #4d6bfe4d}.v78Lda_action:focus-visible{outline:none;box-shadow:0 0 0 2px #7e98ffe6}.v78Lda_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}.v78Lda_summon:hover{color:#c3d3ff;background:#070b1ae6;border-color:#7e98fff2}.v78Lda_summon:active{color:#8ea6ff;border-color:#7e98ffcc}.v78Lda_summon:focus-visible{outline:none;box-shadow:0 0 0 2px #7e98ffe6}@media (prefers-reduced-motion:reduce){.v78Lda_bubble,.v78Lda_bubbleStatus,.v78Lda_bubbleWhisper,.v78Lda_panel{opacity:1;animation:none}.v78Lda_action,.v78Lda_summon,.v78Lda_bubbleMore{transition:none}}.v78Lda_gameplayHud{width:0;height:0;position:absolute;bottom:0;left:0}.v78Lda_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)}.v78Lda_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}.v78Lda_gameplayBars{flex-direction:column;gap:4px;margin-bottom:8px;display:flex}.v78Lda_gameplayBarRow{align-items:center;gap:6px;display:flex}.v78Lda_gameplayBarLabel{color:#9aa8d8;flex-shrink:0;width:34px}.v78Lda_gameplayBarTrack{background:#7e98ff26;border-radius:3px;flex:1;height:6px;overflow:hidden}.v78Lda_gameplayBarFill{background:linear-gradient(90deg,#7e98ff,#9db4ff);border-radius:3px;height:100%;transition:width .3s;display:block}.v78Lda_gameplayActions{flex-wrap:wrap;gap:6px;display:flex}.v78Lda_gameplayShopItems{grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin-bottom:8px;display:grid}.v78Lda_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}.v78Lda_gameplayShopItem:hover{background:#7e98ff29;border-color:#7e98ffb3}.v78Lda_gameplayShopItemImage{object-fit:contain;width:36px;height:36px;image-rendering:pixelated;pointer-events:none}.v78Lda_gameplayShopItemLabel{text-align:center;font-size:11px}.v78Lda_gameplayShopItemPrice{color:#ffd27e;font-size:10px}.v78Lda_gameplaySkinItems{flex-direction:column;gap:6px;margin-bottom:8px;display:flex}.v78Lda_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}.v78Lda_gameplaySkinItem:hover{background:#7e98ff29;border-color:#7e98ffb3}.v78Lda_gameplaySkinItemActive{background:#7e98ff47;border-color:#7e98ffe6}.v78Lda_gameplayClose{color:#9aa8d8;cursor:pointer;background:0 0;border:none;padding:0 2px;font-size:14px;position:absolute;top:4px;right:8px}.v78Lda_gameplayClose:hover{color:#fff}.v78Lda_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 v78Lda_pet-gameplay-float;position:absolute;top:-8px;left:50%;transform:translate(-50%)}@keyframes v78Lda_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){.v78Lda_gameplayFloat{opacity:1;animation:none}.v78Lda_gameplayBarFill{transition:none}}.v78Lda_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) v78Lda_pet-usage-in;display:flex;box-shadow:0 4px 12px #02061759,inset 0 1px #e2e8ff1a,0 0 12px #4d6bfe2e}.v78Lda_bubbleUsageHead{justify-content:space-between;align-items:baseline;gap:10px;display:flex}.v78Lda_bubbleUsageTitle{text-overflow:ellipsis;opacity:.85;overflow:hidden}.v78Lda_bubbleUsageValue{font-variant-numeric:tabular-nums;flex-shrink:0;font-weight:600}.v78Lda_bubbleUsageNote{text-overflow:ellipsis;opacity:.55;font-size:10px;overflow:hidden}.v78Lda_bubbleUsageMeter{background:#e2e8ff24;border-radius:999px;height:4px;display:block;overflow:hidden}.v78Lda_bubbleUsageMeterFill{background:#6ee7b7;border-radius:999px;height:100%;transition:width .3s;display:block}.v78Lda_bubbleUsageOk{border-color:#7e98ff73}.v78Lda_bubbleUsageWarn{border-color:#fbbf248c}.v78Lda_bubbleUsageWarn .v78Lda_bubbleUsageMeterFill{background:#fbbf24}.v78Lda_bubbleUsageLow{border-color:#f8717199}.v78Lda_bubbleUsageLow .v78Lda_bubbleUsageMeterFill{background:#f87171}@keyframes v78Lda_pet-usage-in{0%{opacity:0;transform:translateY(6px)scale(.96)}to{opacity:1;transform:translateY(0)scale(1)}}@media (prefers-reduced-motion:reduce){.v78Lda_bubbleUsage{animation:none}}";
272
+ const tagId$2 = "@linxin666/dsh-pet/src/client/pet.module.css";
273
273
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
274
274
  const tag = document.createElement("style");
275
275
  tag.dataset.plugin = "@linxin666/dsh-pet";
@@ -278,65 +278,65 @@ window.__ModuleLoader__.load({
278
278
  document.head.appendChild(tag);
279
279
  }
280
280
  var pet_module_css_default = {
281
- "action": "kz2Bea_action",
282
- "actions": "kz2Bea_actions",
283
- "bubble": "kz2Bea_bubble",
284
- "bubbleAnchor": "kz2Bea_bubbleAnchor",
285
- "bubbleClickable": "kz2Bea_bubbleClickable",
286
- "bubbleFeed": "kz2Bea_bubbleFeed",
287
- "bubbleMore": "kz2Bea_bubbleMore",
288
- "bubblePet": "kz2Bea_bubblePet",
289
- "bubbleStack": "kz2Bea_bubbleStack",
290
- "bubbleStatus": "kz2Bea_bubbleStatus",
291
- "bubbleUsage": "kz2Bea_bubbleUsage",
292
- "bubbleUsageHead": "kz2Bea_bubbleUsageHead",
293
- "bubbleUsageLow": "kz2Bea_bubbleUsageLow",
294
- "bubbleUsageMeter": "kz2Bea_bubbleUsageMeter",
295
- "bubbleUsageMeterFill": "kz2Bea_bubbleUsageMeterFill",
296
- "bubbleUsageNote": "kz2Bea_bubbleUsageNote",
297
- "bubbleUsageOk": "kz2Bea_bubbleUsageOk",
298
- "bubbleUsageTitle": "kz2Bea_bubbleUsageTitle",
299
- "bubbleUsageValue": "kz2Bea_bubbleUsageValue",
300
- "bubbleUsageWarn": "kz2Bea_bubbleUsageWarn",
301
- "bubbleWhisper": "kz2Bea_bubbleWhisper",
302
- "float": "kz2Bea_float",
303
- "gameplayActions": "kz2Bea_gameplayActions",
304
- "gameplayBarFill": "kz2Bea_gameplayBarFill",
305
- "gameplayBarLabel": "kz2Bea_gameplayBarLabel",
306
- "gameplayBarRow": "kz2Bea_gameplayBarRow",
307
- "gameplayBarTrack": "kz2Bea_gameplayBarTrack",
308
- "gameplayBars": "kz2Bea_gameplayBars",
309
- "gameplayCard": "kz2Bea_gameplayCard",
310
- "gameplayClose": "kz2Bea_gameplayClose",
311
- "gameplayFloat": "kz2Bea_gameplayFloat",
312
- "gameplayHud": "kz2Bea_gameplayHud",
313
- "gameplayModeChip": "kz2Bea_gameplayModeChip",
314
- "gameplayShopItem": "kz2Bea_gameplayShopItem",
315
- "gameplayShopItemImage": "kz2Bea_gameplayShopItemImage",
316
- "gameplayShopItemLabel": "kz2Bea_gameplayShopItemLabel",
317
- "gameplayShopItemPrice": "kz2Bea_gameplayShopItemPrice",
318
- "gameplayShopItems": "kz2Bea_gameplayShopItems",
319
- "gameplaySkinItem": "kz2Bea_gameplaySkinItem",
320
- "gameplaySkinItemActive": "kz2Bea_gameplaySkinItemActive",
321
- "gameplaySkinItems": "kz2Bea_gameplaySkinItems",
322
- "nameCell": "kz2Bea_nameCell",
323
- "nameInput": "kz2Bea_nameInput",
324
- "panel": "kz2Bea_panel",
325
- "panelAbove": "kz2Bea_panelAbove",
326
- "pet-bubble-in": "kz2Bea_pet-bubble-in",
327
- "pet-bubble-pop": "kz2Bea_pet-bubble-pop",
328
- "pet-gameplay-float": "kz2Bea_pet-gameplay-float",
329
- "pet-panel-in": "kz2Bea_pet-panel-in",
330
- "pet-usage-in": "kz2Bea_pet-usage-in",
331
- "pet-whisper-in": "kz2Bea_pet-whisper-in",
332
- "rankRow": "kz2Bea_rankRow",
333
- "renameRow": "kz2Bea_renameRow",
334
- "sprite": "kz2Bea_sprite",
335
- "spriteWrap": "kz2Bea_spriteWrap",
336
- "statPoints": "kz2Bea_statPoints",
337
- "statRank": "kz2Bea_statRank",
338
- "statTreats": "kz2Bea_statTreats",
339
- "summon": "kz2Bea_summon"
281
+ "action": "v78Lda_action",
282
+ "actions": "v78Lda_actions",
283
+ "bubble": "v78Lda_bubble",
284
+ "bubbleAnchor": "v78Lda_bubbleAnchor",
285
+ "bubbleClickable": "v78Lda_bubbleClickable",
286
+ "bubbleFeed": "v78Lda_bubbleFeed",
287
+ "bubbleMore": "v78Lda_bubbleMore",
288
+ "bubblePet": "v78Lda_bubblePet",
289
+ "bubbleStack": "v78Lda_bubbleStack",
290
+ "bubbleStatus": "v78Lda_bubbleStatus",
291
+ "bubbleUsage": "v78Lda_bubbleUsage",
292
+ "bubbleUsageHead": "v78Lda_bubbleUsageHead",
293
+ "bubbleUsageLow": "v78Lda_bubbleUsageLow",
294
+ "bubbleUsageMeter": "v78Lda_bubbleUsageMeter",
295
+ "bubbleUsageMeterFill": "v78Lda_bubbleUsageMeterFill",
296
+ "bubbleUsageNote": "v78Lda_bubbleUsageNote",
297
+ "bubbleUsageOk": "v78Lda_bubbleUsageOk",
298
+ "bubbleUsageTitle": "v78Lda_bubbleUsageTitle",
299
+ "bubbleUsageValue": "v78Lda_bubbleUsageValue",
300
+ "bubbleUsageWarn": "v78Lda_bubbleUsageWarn",
301
+ "bubbleWhisper": "v78Lda_bubbleWhisper",
302
+ "float": "v78Lda_float",
303
+ "gameplayActions": "v78Lda_gameplayActions",
304
+ "gameplayBarFill": "v78Lda_gameplayBarFill",
305
+ "gameplayBarLabel": "v78Lda_gameplayBarLabel",
306
+ "gameplayBarRow": "v78Lda_gameplayBarRow",
307
+ "gameplayBarTrack": "v78Lda_gameplayBarTrack",
308
+ "gameplayBars": "v78Lda_gameplayBars",
309
+ "gameplayCard": "v78Lda_gameplayCard",
310
+ "gameplayClose": "v78Lda_gameplayClose",
311
+ "gameplayFloat": "v78Lda_gameplayFloat",
312
+ "gameplayHud": "v78Lda_gameplayHud",
313
+ "gameplayModeChip": "v78Lda_gameplayModeChip",
314
+ "gameplayShopItem": "v78Lda_gameplayShopItem",
315
+ "gameplayShopItemImage": "v78Lda_gameplayShopItemImage",
316
+ "gameplayShopItemLabel": "v78Lda_gameplayShopItemLabel",
317
+ "gameplayShopItemPrice": "v78Lda_gameplayShopItemPrice",
318
+ "gameplayShopItems": "v78Lda_gameplayShopItems",
319
+ "gameplaySkinItem": "v78Lda_gameplaySkinItem",
320
+ "gameplaySkinItemActive": "v78Lda_gameplaySkinItemActive",
321
+ "gameplaySkinItems": "v78Lda_gameplaySkinItems",
322
+ "nameCell": "v78Lda_nameCell",
323
+ "nameInput": "v78Lda_nameInput",
324
+ "panel": "v78Lda_panel",
325
+ "panelAbove": "v78Lda_panelAbove",
326
+ "pet-bubble-in": "v78Lda_pet-bubble-in",
327
+ "pet-bubble-pop": "v78Lda_pet-bubble-pop",
328
+ "pet-gameplay-float": "v78Lda_pet-gameplay-float",
329
+ "pet-panel-in": "v78Lda_pet-panel-in",
330
+ "pet-usage-in": "v78Lda_pet-usage-in",
331
+ "pet-whisper-in": "v78Lda_pet-whisper-in",
332
+ "rankRow": "v78Lda_rankRow",
333
+ "renameRow": "v78Lda_renameRow",
334
+ "sprite": "v78Lda_sprite",
335
+ "spriteWrap": "v78Lda_spriteWrap",
336
+ "statPoints": "v78Lda_statPoints",
337
+ "statRank": "v78Lda_statRank",
338
+ "statTreats": "v78Lda_statTreats",
339
+ "summon": "v78Lda_summon"
340
340
  };
341
341
  //#endregion
342
342
  //#region src/client/PetSprite.tsx
@@ -618,10 +618,12 @@ window.__ModuleLoader__.load({
618
618
  st.elapsed -= track.durations[st.index] ?? 0;
619
619
  st.index += 1;
620
620
  }
621
- if (st.elapsed >= (track.durations[st.index] ?? 0)) if (track.loop) {
622
- st.elapsed = 0;
623
- st.index = 0;
624
- } else st.index = maxIndex;
621
+ if (st.elapsed >= (track.durations[st.index] ?? 0)) {
622
+ if (track.loop) {
623
+ st.elapsed = 0;
624
+ st.index = 0;
625
+ } else st.index = maxIndex;
626
+ }
625
627
  const col = track.frames[st.index];
626
628
  const pos = framePosition(cell, row, col, scaleRef.current);
627
629
  const posStr = pos.x + "px " + pos.y + "px";
@@ -660,6 +662,7 @@ window.__ModuleLoader__.load({
660
662
  (0, react.useEffect)(() => () => clearHideTimer(), []);
661
663
  const onPointerDown = (e) => {
662
664
  if (props.dragDisabled === true) return;
665
+ endWalk(false);
663
666
  e.preventDefault();
664
667
  e.target.setPointerCapture?.(e.pointerId);
665
668
  const current = dragPos ?? {
@@ -702,6 +705,84 @@ window.__ModuleLoader__.load({
702
705
  };
703
706
  const spriteWidth = Math.round(cell.width * spriteScale);
704
707
  const spriteHeight = Math.round(cell.height * spriteScale);
708
+ const [facingRight, setFacingRight] = (0, react.useState)(false);
709
+ const walkRafRef = (0, react.useRef)(0);
710
+ const walkTargetRef = (0, react.useRef)(null);
711
+ const dragPosRef = (0, react.useRef)(dragPos);
712
+ dragPosRef.current = dragPos;
713
+ /** Stop an active walk; `persist` lands the pet on the spot it reached. */
714
+ const endWalk = (persist) => {
715
+ if (walkRafRef.current === 0) return;
716
+ window.cancelAnimationFrame(walkRafRef.current);
717
+ walkRafRef.current = 0;
718
+ setFacingRight(false);
719
+ const settled = walkTargetRef.current;
720
+ walkTargetRef.current = null;
721
+ if (persist && settled !== null) props.onDragEnd(settled.right, settled.bottom);
722
+ };
723
+ (0, react.useEffect)(() => {
724
+ const bus = props.bus;
725
+ if (bus === void 0) return void 0;
726
+ bus.walk = (direction, distance, speed) => {
727
+ if (dragRef.current !== null || walkRafRef.current !== 0) return 0;
728
+ const current = dragPosRef.current ?? {
729
+ right: display.right,
730
+ bottom: display.bottom
731
+ };
732
+ const margin = 8;
733
+ const maxRight = Math.max(margin, window.innerWidth - spriteWidth - margin);
734
+ const maxBottom = Math.max(margin, window.innerHeight - spriteHeight - margin);
735
+ const wanted = { ...current };
736
+ if (direction === "left") wanted.right = current.right + distance;
737
+ else if (direction === "right") wanted.right = current.right - distance;
738
+ else if (direction === "up") wanted.bottom = current.bottom + distance;
739
+ else wanted.bottom = current.bottom - distance;
740
+ const target = {
741
+ right: Math.max(margin, clampOffset(wanted.right, maxRight)),
742
+ bottom: Math.max(margin, clampOffset(wanted.bottom, maxBottom))
743
+ };
744
+ const travelled = direction === "left" ? target.right - current.right : direction === "right" ? current.right - target.right : direction === "up" ? target.bottom - current.bottom : current.bottom - target.bottom;
745
+ if (travelled < 1) return 0;
746
+ const duration = Math.max(150, travelled / Math.max(1, speed) * 1e3);
747
+ const startedAt = performance.now();
748
+ setFacingRight(direction === "right");
749
+ walkTargetRef.current = current;
750
+ const step = (now) => {
751
+ const t = Math.min(1, (now - startedAt) / duration);
752
+ const next = {
753
+ right: current.right + (target.right - current.right) * t,
754
+ bottom: current.bottom + (target.bottom - current.bottom) * t
755
+ };
756
+ walkTargetRef.current = next;
757
+ setDragPos(next);
758
+ if (t < 1) {
759
+ walkRafRef.current = window.requestAnimationFrame(step);
760
+ return;
761
+ }
762
+ walkRafRef.current = 0;
763
+ walkTargetRef.current = null;
764
+ setFacingRight(false);
765
+ props.onDragEnd(next.right, next.bottom);
766
+ };
767
+ walkRafRef.current = window.requestAnimationFrame(step);
768
+ return travelled;
769
+ };
770
+ return () => {
771
+ bus.walk = void 0;
772
+ if (walkRafRef.current !== 0) {
773
+ window.cancelAnimationFrame(walkRafRef.current);
774
+ walkRafRef.current = 0;
775
+ }
776
+ setFacingRight(false);
777
+ walkTargetRef.current = null;
778
+ };
779
+ }, [
780
+ props.bus,
781
+ definition.id,
782
+ display.right,
783
+ display.bottom,
784
+ spriteWidth
785
+ ]);
705
786
  const bubbleScale = bubbleScaleFor(display);
706
787
  const sessionBubbles = snapshot?.sessions ?? [];
707
788
  const stackOpen = stackPeek || stackPinned;
@@ -744,7 +825,7 @@ window.__ModuleLoader__.load({
744
825
  stackOpen,
745
826
  feedback
746
827
  ]);
747
- return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
828
+ const float = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
748
829
  ref: floatRef,
749
830
  className: pet_module_css_default.float,
750
831
  style: {
@@ -783,7 +864,8 @@ window.__ModuleLoader__.load({
783
864
  backgroundRepeat: "no-repeat",
784
865
  backgroundPosition: "0 0"
785
866
  } : {},
786
- cursor: dragRef.current === null ? "grab" : "grabbing"
867
+ cursor: dragRef.current === null ? "grab" : "grabbing",
868
+ ...facingRight ? { transform: "scaleX(-1)" } : {}
787
869
  },
788
870
  onPointerDown,
789
871
  onPointerMove,
@@ -963,7 +1045,8 @@ window.__ModuleLoader__.load({
963
1045
  ] })
964
1046
  })
965
1047
  ]
966
- }), props.portalTarget ?? document.body);
1048
+ });
1049
+ return (0, react_dom.createPortal)(float, props.portalTarget ?? document.body);
967
1050
  }
968
1051
  //#endregion
969
1052
  //#region src/client/drag-stream.ts
@@ -1276,6 +1359,36 @@ window.__ModuleLoader__.load({
1276
1359
  }
1277
1360
  //#endregion
1278
1361
  //#region src/gameplay.ts
1362
+ /** Every roam direction, in roll order (equal chance each unless restricted). */
1363
+ const PET_ROAM_DIRECTIONS = [
1364
+ "up",
1365
+ "down",
1366
+ "left",
1367
+ "right"
1368
+ ];
1369
+ /**
1370
+ * One declared extra mode, by OWN key only. A plain `modes[id]` lookup also
1371
+ * answers for Object.prototype members ('constructor', 'toString', …), which
1372
+ * would let a crafted mode id pass the "is it declared?" test.
1373
+ */
1374
+ function declaredModeOf(manifest, mode) {
1375
+ const modes = manifest.modes;
1376
+ if (modes === void 0 || !Object.prototype.hasOwnProperty.call(modes, mode)) return void 0;
1377
+ return modes[mode];
1378
+ }
1379
+ /** Every mode the menu offers, in manifest order (sleep first, then extras). */
1380
+ function declaredModes(manifest) {
1381
+ return [...manifest.sleep === void 0 ? [] : ["sleep"], ...Object.keys(manifest.modes ?? {})];
1382
+ }
1383
+ /**
1384
+ * The frames2d track one active mode holds. 'work' is owned by the work loop
1385
+ * (its state, result and fallback are its own), so it resolves to undefined.
1386
+ */
1387
+ function modeStateOf(manifest, mode) {
1388
+ if (mode === "sleep") return manifest.sleep?.state;
1389
+ if (mode === "work") return manifest.work?.state;
1390
+ return declaredModeOf(manifest, mode)?.state;
1391
+ }
1279
1392
  /** The zone one normalized hit-box point lands in, if any. */
1280
1393
  function touchZoneAt(touch, yFraction) {
1281
1394
  return touch.zones.find((zone) => yFraction >= zone.y0 && yFraction < zone.y1);
@@ -1299,7 +1412,6 @@ window.__ModuleLoader__.load({
1299
1412
  const ui = (0, react.useSyncExternalStore)(store.subscribe, store.getSnapshot);
1300
1413
  const def = definition.gameplay;
1301
1414
  const view = ui.snapshot?.gameplay;
1302
- const phase = ui.snapshot?.phase ?? "idle";
1303
1415
  const persistedSkin = ui.snapshot?.skin;
1304
1416
  const [open, setOpen] = (0, react.useState)(false);
1305
1417
  const [page, setPage] = (0, react.useState)("root");
@@ -1311,12 +1423,19 @@ window.__ModuleLoader__.load({
1311
1423
  const [floats, setFloats] = (0, react.useState)([]);
1312
1424
  const modeRef = (0, react.useRef)(view?.mode ?? null);
1313
1425
  modeRef.current = view?.mode ?? null;
1314
- const phaseRef = (0, react.useRef)(phase);
1315
- phaseRef.current = phase;
1426
+ /** Live gameplay view for the interval loops (def identity is stable, view is not). */
1427
+ const viewRef = (0, react.useRef)(view);
1428
+ viewRef.current = view;
1316
1429
  const draggingRef = (0, react.useRef)(false);
1317
1430
  const touchLockUntilRef = (0, react.useRef)(0);
1318
1431
  const missRef = (0, react.useRef)(0);
1319
1432
  const busyRef = (0, react.useRef)(false);
1433
+ /** A roam walk owns the visual right now (the idle director must not steal it). */
1434
+ const roamHeldRef = (0, react.useRef)(false);
1435
+ /** A one-shot idle-director act owns the visual right now (the roam must not cut in). */
1436
+ const actHeldRef = (0, react.useRef)(false);
1437
+ /** Pending release of the roam's walk track (cleared when another owner takes it). */
1438
+ const roamTimerRef = (0, react.useRef)(0);
1320
1439
  const tr = props.t;
1321
1440
  const statLabel = (name) => tr("pet.gameplay.stat." + name);
1322
1441
  const currencyLabel = (name) => tr("pet.gameplay.currency." + name);
@@ -1333,9 +1452,23 @@ window.__ModuleLoader__.load({
1333
1452
  const applyResult = (result) => {
1334
1453
  if (result.view !== void 0) store.actions.setGameplayView(result.view);
1335
1454
  };
1455
+ /**
1456
+ * Give up the roam's claim on the shared track-override slot. Every other
1457
+ * owner (a mode, a touch reaction, a drag) calls this before it takes the
1458
+ * slot, so a walk that is still in flight can never release someone else's
1459
+ * track when its own hold window elapses.
1460
+ */
1461
+ const yieldRoam = () => {
1462
+ if (roamTimerRef.current !== 0) {
1463
+ window.clearTimeout(roamTimerRef.current);
1464
+ roamTimerRef.current = 0;
1465
+ }
1466
+ roamHeldRef.current = false;
1467
+ };
1336
1468
  (0, react.useEffect)(() => {
1337
1469
  if (def === void 0) return void 0;
1338
1470
  const holdTrack = (track, holdMs) => {
1471
+ yieldRoam();
1339
1472
  bus.setTrack?.(track);
1340
1473
  touchLockUntilRef.current = Date.now() + holdMs;
1341
1474
  window.setTimeout(() => {
@@ -1354,7 +1487,7 @@ window.__ModuleLoader__.load({
1354
1487
  };
1355
1488
  const trackDuration = (track) => definition.frames2d?.tracks[track]?.durations.reduce((sum, ms) => sum + ms, 0) ?? 0;
1356
1489
  bus.tap = (fx, fy) => {
1357
- if (modeRef.current === "sleep") {
1490
+ if (modeRef.current !== null && modeRef.current !== "work") {
1358
1491
  api.setMode(null).then(applyResult, () => void 0);
1359
1492
  return;
1360
1493
  }
@@ -1440,7 +1573,8 @@ window.__ModuleLoader__.load({
1440
1573
  (0, react.useEffect)(() => {
1441
1574
  return props.drag.subscribe((dragging) => {
1442
1575
  draggingRef.current = dragging;
1443
- if (dragging && modeRef.current === "sleep") api.setMode(null).then(applyResult, () => void 0);
1576
+ if (dragging) yieldRoam();
1577
+ if (dragging && modeRef.current !== null && modeRef.current !== "work") api.setMode(null).then(applyResult, () => void 0);
1444
1578
  });
1445
1579
  }, [props.drag]);
1446
1580
  (0, react.useEffect)(() => {
@@ -1448,10 +1582,12 @@ window.__ModuleLoader__.load({
1448
1582
  if (def === void 0 || director === void 0) return void 0;
1449
1583
  const total = director.idleWeight + director.acts.reduce((sum, act) => sum + act.weight, 0);
1450
1584
  if (total <= 0) return void 0;
1585
+ let actTimer = 0;
1451
1586
  const timer = window.setInterval(() => {
1452
- if (phaseRef.current !== "idle") return;
1453
1587
  if (modeRef.current !== null || draggingRef.current) return;
1454
1588
  if (Date.now() < touchLockUntilRef.current) return;
1589
+ if (roamHeldRef.current) return;
1590
+ if (actHeldRef.current) return;
1455
1591
  let pickedAct;
1456
1592
  if (missRef.current >= director.maxMiss) {
1457
1593
  const actTotal = director.acts.reduce((sum, act) => sum + act.weight, 0);
@@ -1478,7 +1614,13 @@ window.__ModuleLoader__.load({
1478
1614
  return;
1479
1615
  }
1480
1616
  missRef.current = 0;
1617
+ actHeldRef.current = true;
1481
1618
  bus.setTrack?.(pickedAct.track);
1619
+ const holdMs = definition.frames2d?.tracks[pickedAct.track]?.durations.reduce((sum, ms) => sum + ms, 0) ?? 0;
1620
+ window.clearTimeout(actTimer);
1621
+ actTimer = window.setTimeout(() => {
1622
+ actHeldRef.current = false;
1623
+ }, holdMs > 0 ? holdMs : 3e3);
1482
1624
  if (pickedAct.phrases !== void 0 && pickedAct.phrases.length > 0) {
1483
1625
  const phrase = pickedAct.phrases[Math.floor(Math.random() * pickedAct.phrases.length)];
1484
1626
  store.actions.setFeedback({
@@ -1488,7 +1630,11 @@ window.__ModuleLoader__.load({
1488
1630
  });
1489
1631
  }
1490
1632
  }, director.intervalMs);
1491
- return () => window.clearInterval(timer);
1633
+ return () => {
1634
+ window.clearInterval(timer);
1635
+ window.clearTimeout(actTimer);
1636
+ actHeldRef.current = false;
1637
+ };
1492
1638
  }, [definition.id, def]);
1493
1639
  (0, react.useEffect)(() => {
1494
1640
  const work = def?.work;
@@ -1528,10 +1674,13 @@ window.__ModuleLoader__.load({
1528
1674
  skinId
1529
1675
  ]);
1530
1676
  (0, react.useEffect)(() => {
1531
- const sleep = def?.sleep;
1532
- if (def === void 0 || sleep === void 0 || view?.mode !== "sleep") return void 0;
1533
- const hold = (definition.frames2d?.skins?.find((skin) => skin.id === skinIdRef.current)?.gameplayTracks)?.["sleep"] ?? sleep.state;
1534
- bus.setTrack?.(hold);
1677
+ const active = view?.mode;
1678
+ if (def === void 0 || active === void 0 || active === null || active === "work") return void 0;
1679
+ const hold = modeStateOf(def, active);
1680
+ if (hold === void 0) return void 0;
1681
+ const skinGameplay = definition.frames2d?.skins?.find((skin) => skin.id === skinIdRef.current)?.gameplayTracks;
1682
+ yieldRoam();
1683
+ bus.setTrack?.(skinGameplay?.[active] ?? hold);
1535
1684
  return () => bus.setTrack?.(void 0);
1536
1685
  }, [
1537
1686
  definition.id,
@@ -1539,10 +1688,58 @@ window.__ModuleLoader__.load({
1539
1688
  view?.mode,
1540
1689
  skinId
1541
1690
  ]);
1691
+ (0, react.useEffect)(() => {
1692
+ const roam = def?.roam;
1693
+ if (def === void 0 || roam === void 0) return void 0;
1694
+ const roll = () => {
1695
+ if (modeRef.current !== null || draggingRef.current) return;
1696
+ if (Date.now() < touchLockUntilRef.current) return;
1697
+ if (roamHeldRef.current || actHeldRef.current) return;
1698
+ if (Math.random() >= roam.probability) return;
1699
+ const span = roam.distanceMax - roam.distanceMin;
1700
+ const distance = roam.distanceMin + Math.random() * span;
1701
+ const directions = roam.directions ?? PET_ROAM_DIRECTIONS;
1702
+ const direction = directions[Math.floor(Math.random() * directions.length)] ?? "left";
1703
+ const travelled = bus.walk?.(direction, distance, roam.speed) ?? 0;
1704
+ if (travelled === 0) return;
1705
+ roamHeldRef.current = true;
1706
+ bus.setTrack?.(roam.state);
1707
+ roamTimerRef.current = window.setTimeout(() => {
1708
+ roamTimerRef.current = 0;
1709
+ if (!roamHeldRef.current) return;
1710
+ roamHeldRef.current = false;
1711
+ bus.setTrack?.(void 0);
1712
+ }, Math.max(150, Math.abs(travelled) / roam.speed * 1e3));
1713
+ };
1714
+ let timer = 0;
1715
+ const lead = window.setTimeout(() => {
1716
+ roll();
1717
+ timer = window.setInterval(roll, roam.intervalMs);
1718
+ }, Math.round(roam.intervalMs / 2));
1719
+ return () => {
1720
+ window.clearTimeout(lead);
1721
+ window.clearInterval(timer);
1722
+ yieldRoam();
1723
+ };
1724
+ }, [definition.id, def]);
1542
1725
  if (def === void 0 || view === void 0) return null;
1543
1726
  const mode = view.mode;
1544
1727
  const stats = def.stats ?? {};
1545
1728
  const shop = def.shop;
1729
+ const menuModes = declaredModes(def);
1730
+ /** Action-button label for one mode (active = the button that leaves it). */
1731
+ const modeLabel = (name, active) => {
1732
+ if (name === "sleep") return tr(active ? "pet.gameplay.wake" : "pet.gameplay.sleep");
1733
+ const declared = def.modes?.[name];
1734
+ return (active ? declared?.activeLabel ?? declared?.label : declared?.label) ?? tr("pet.gameplay." + name);
1735
+ };
1736
+ /** Chip label for the mode the pet is in right now. */
1737
+ const modeChip = (name) => {
1738
+ if (name === "work") return tr("pet.gameplay.working");
1739
+ if (name === "sleep") return tr("pet.gameplay.sleeping");
1740
+ const declared = def.modes?.[name];
1741
+ return declared?.activeLabel ?? declared?.label ?? tr("pet.gameplay." + name);
1742
+ };
1546
1743
  const buy = (itemId) => {
1547
1744
  api.buy(itemId).then((result) => {
1548
1745
  applyResult(result);
@@ -1600,7 +1797,7 @@ window.__ModuleLoader__.load({
1600
1797
  }, entry.id)),
1601
1798
  mode !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1602
1799
  className: pet_module_css_default.gameplayModeChip,
1603
- children: tr(mode === "work" ? "pet.gameplay.working" : "pet.gameplay.sleeping")
1800
+ children: modeChip(mode)
1604
1801
  }),
1605
1802
  open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1606
1803
  ref: cardRef,
@@ -1629,12 +1826,12 @@ window.__ModuleLoader__.load({
1629
1826
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1630
1827
  className: pet_module_css_default.gameplayActions,
1631
1828
  children: [
1632
- def.sleep !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1829
+ menuModes.map((name) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1633
1830
  type: "button",
1634
1831
  className: pet_module_css_default.action,
1635
- onClick: () => setMode(mode === "sleep" ? null : "sleep"),
1636
- children: tr(mode === "sleep" ? "pet.gameplay.wake" : "pet.gameplay.sleep")
1637
- }),
1832
+ onClick: () => setMode(mode === name ? null : name),
1833
+ children: modeLabel(name, mode === name)
1834
+ }, name)),
1638
1835
  shop !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1639
1836
  type: "button",
1640
1837
  className: pet_module_css_default.action,
@@ -1799,6 +1996,7 @@ window.__ModuleLoader__.load({
1799
1996
  onFeedbackDone: props.feedbackDone,
1800
1997
  portalTarget: props.portalTarget,
1801
1998
  dragDisabled: snapshot.gameplay?.mode === "work",
1999
+ ...aux === null ? {} : { bus: aux.bus },
1802
2000
  ...gameplay === void 0 || aux === null ? {} : {
1803
2001
  onGameplayTap: (fx, fy) => aux.bus.tap?.(fx, fy),
1804
2002
  onGameplayMenu: () => aux.bus.openCard?.(),
@@ -2513,9 +2711,9 @@ window.__ModuleLoader__.load({
2513
2711
  teardown?.();
2514
2712
  }
2515
2713
  //#endregion
2516
- //#region \0dsh-css:packages/dsh-pet/src/client/settings-card.module.css.mjs
2517
- const css$1 = ".kKk9aW_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.kKk9aW_card:hover{border-color:var(--dsw-alias-label-dimmed)}.kKk9aW_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.kKk9aW_header{appearance:none;box-sizing:border-box;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.kKk9aW_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.kKk9aW_headerStatic{box-sizing:border-box;border-radius:12px;align-items:center;gap:12px;width:100%;padding:14px 16px;display:flex}.kKk9aW_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.kKk9aW_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.kKk9aW_description{color:var(--dsw-alias-label-secondary);font-size:13px;line-height:1.5}.kKk9aW_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.kKk9aW_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.kKk9aW_chevronOpen{transform:rotate(180deg)}.kKk9aW_body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.kKk9aW_readOnly{color:var(--dsw-alias-label-secondary);margin:12px 0 0;font-size:12px;line-height:1.5}.kKk9aW_notExposed{color:var(--dsw-alias-state-warn-primary);margin:12px 0 0;font-size:12px;line-height:1.5}.kKk9aW_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.kKk9aW_failed{min-width:0;color:var(--dsw-alias-state-error-primary,#b42318);text-overflow:ellipsis;white-space:nowrap;flex:1;margin:0;font-size:12px;line-height:1.5;overflow:hidden}.kKk9aW_discard,.kKk9aW_save{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.kKk9aW_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.kKk9aW_discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.kKk9aW_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.kKk9aW_discard:disabled,.kKk9aW_save:disabled{opacity:.4;cursor:default}.kKk9aW_discard:focus-visible,.kKk9aW_save:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.kKk9aW_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.kKk9aW_field+.kKk9aW_field{border-top:1px solid var(--dsw-alias-border-l2)}.kKk9aW_head{align-items:center;gap:8px;display:flex}.kKk9aW_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.kKk9aW_badges{align-items:center;gap:8px;display:inline-flex}.kKk9aW_badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.kKk9aW_reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.kKk9aW_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.kKk9aW_reset:disabled{cursor:default}.kKk9aW_reset:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px;outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.kKk9aW_input,.kKk9aW_select{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.kKk9aW_input:focus-visible,.kKk9aW_select:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.kKk9aW_input:disabled,.kKk9aW_select:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.kKk9aW_inputInvalid{border:1px solid var(--dsw-alias-state-error-primary,#b42318);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.kKk9aW_inputInvalid:focus-visible{outline:2px solid var(--dsw-alias-state-error-primary,#b42318);outline-offset:1px;border-color:var(--dsw-alias-state-error-primary,#b42318)}.kKk9aW_selectWrap{position:relative}.kKk9aW_selectButton{appearance:none;text-align:left;cursor:pointer;justify-content:space-between;align-items:center;gap:8px;width:100%;display:flex}.kKk9aW_selectLabel{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.kKk9aW_selectChevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.kKk9aW_selectChevronOpen{transform:rotate(180deg)}.kKk9aW_selectPopup{z-index:40;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);max-height:240px;box-shadow:0 8px 24px var(--dsw-alias-bg-mask-2);opacity:0;border-radius:8px;flex-direction:column;padding:4px;transition:opacity .1s,transform .1s;display:flex;position:absolute;top:calc(100% + 4px);left:0;right:0;overflow-y:auto;transform:translateY(-4px)}.kKk9aW_selectPopupOpen{opacity:1;transform:none}.kKk9aW_selectPopupClose{opacity:0;pointer-events:none;transform:translateY(-4px)}.kKk9aW_selectOption{color:var(--dsw-alias-label-primary);cursor:pointer;white-space:nowrap;text-overflow:ellipsis;border-radius:6px;flex-shrink:0;padding:6px 10px;font-size:13px;line-height:1.5;overflow:hidden}.kKk9aW_selectOption:hover,.kKk9aW_selectOptionActive{background:var(--dsw-alias-interactive-bg-hover)}.kKk9aW_selectOptionSelected{color:var(--dsw-alias-brand-primary);background:color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 10%, transparent);font-weight:500}.kKk9aW_invalid{color:var(--dsw-alias-state-error-primary,#b42318);margin:0;font-size:12px;line-height:1.5}.kKk9aW_hint{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:1.5}@media (prefers-reduced-motion:reduce){.kKk9aW_card,.kKk9aW_header,.kKk9aW_chevron,.kKk9aW_chevronOpen,.kKk9aW_discard,.kKk9aW_save,.kKk9aW_selectChevron,.kKk9aW_selectChevronOpen,.kKk9aW_selectPopup{transition:none}}";
2518
- const tagId$1 = "@linxin666/dsh-pet/packages/dsh-pet/src/client/settings-card.module.css";
2714
+ //#region \0dsh-css:src/client/settings-card.module.css.mjs
2715
+ const css$1 = ".KApx_W_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.KApx_W_card:hover{border-color:var(--dsw-alias-label-dimmed)}.KApx_W_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.KApx_W_header{appearance:none;box-sizing:border-box;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.KApx_W_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.KApx_W_headerStatic{box-sizing:border-box;border-radius:12px;align-items:center;gap:12px;width:100%;padding:14px 16px;display:flex}.KApx_W_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.KApx_W_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.KApx_W_description{color:var(--dsw-alias-label-secondary);font-size:13px;line-height:1.5}.KApx_W_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.KApx_W_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.KApx_W_chevronOpen{transform:rotate(180deg)}.KApx_W_body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.KApx_W_readOnly{color:var(--dsw-alias-label-secondary);margin:12px 0 0;font-size:12px;line-height:1.5}.KApx_W_notExposed{color:var(--dsw-alias-state-warn-primary);margin:12px 0 0;font-size:12px;line-height:1.5}.KApx_W_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.KApx_W_failed{min-width:0;color:var(--dsw-alias-state-error-primary,#b42318);text-overflow:ellipsis;white-space:nowrap;flex:1;margin:0;font-size:12px;line-height:1.5;overflow:hidden}.KApx_W_discard,.KApx_W_save{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.KApx_W_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.KApx_W_discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.KApx_W_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.KApx_W_discard:disabled,.KApx_W_save:disabled{opacity:.4;cursor:default}.KApx_W_discard:focus-visible,.KApx_W_save:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.KApx_W_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.KApx_W_field+.KApx_W_field{border-top:1px solid var(--dsw-alias-border-l2)}.KApx_W_head{align-items:center;gap:8px;display:flex}.KApx_W_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.KApx_W_badges{align-items:center;gap:8px;display:inline-flex}.KApx_W_badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.KApx_W_reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.KApx_W_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.KApx_W_reset:disabled{cursor:default}.KApx_W_reset:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px;outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.KApx_W_input,.KApx_W_select{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.KApx_W_input:focus-visible,.KApx_W_select:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.KApx_W_input:disabled,.KApx_W_select:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.KApx_W_inputInvalid{border:1px solid var(--dsw-alias-state-error-primary,#b42318);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.KApx_W_inputInvalid:focus-visible{outline:2px solid var(--dsw-alias-state-error-primary,#b42318);outline-offset:1px;border-color:var(--dsw-alias-state-error-primary,#b42318)}.KApx_W_selectWrap{position:relative}.KApx_W_selectButton{appearance:none;text-align:left;cursor:pointer;justify-content:space-between;align-items:center;gap:8px;width:100%;display:flex}.KApx_W_selectLabel{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.KApx_W_selectChevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.KApx_W_selectChevronOpen{transform:rotate(180deg)}.KApx_W_selectPopup{z-index:40;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);max-height:240px;box-shadow:0 8px 24px var(--dsw-alias-bg-mask-2);opacity:0;border-radius:8px;flex-direction:column;padding:4px;transition:opacity .1s,transform .1s;display:flex;position:absolute;top:calc(100% + 4px);left:0;right:0;overflow-y:auto;transform:translateY(-4px)}.KApx_W_selectPopupOpen{opacity:1;transform:none}.KApx_W_selectPopupClose{opacity:0;pointer-events:none;transform:translateY(-4px)}.KApx_W_selectOption{color:var(--dsw-alias-label-primary);cursor:pointer;white-space:nowrap;text-overflow:ellipsis;border-radius:6px;flex-shrink:0;padding:6px 10px;font-size:13px;line-height:1.5;overflow:hidden}.KApx_W_selectOption:hover,.KApx_W_selectOptionActive{background:var(--dsw-alias-interactive-bg-hover)}.KApx_W_selectOptionSelected{color:var(--dsw-alias-brand-primary);background:color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 10%, transparent);font-weight:500}.KApx_W_invalid{color:var(--dsw-alias-state-error-primary,#b42318);margin:0;font-size:12px;line-height:1.5}.KApx_W_hint{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:1.5}@media (prefers-reduced-motion:reduce){.KApx_W_card,.KApx_W_header,.KApx_W_chevron,.KApx_W_chevronOpen,.KApx_W_discard,.KApx_W_save,.KApx_W_selectChevron,.KApx_W_selectChevronOpen,.KApx_W_selectPopup{transition:none}}";
2716
+ const tagId$1 = "@linxin666/dsh-pet/src/client/settings-card.module.css";
2519
2717
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
2520
2718
  const tag = document.createElement("style");
2521
2719
  tag.dataset.plugin = "@linxin666/dsh-pet";
@@ -2524,45 +2722,45 @@ window.__ModuleLoader__.load({
2524
2722
  document.head.appendChild(tag);
2525
2723
  }
2526
2724
  var settings_card_module_css_default = {
2527
- "badge": "kKk9aW_badge",
2528
- "badges": "kKk9aW_badges",
2529
- "body": "kKk9aW_body",
2530
- "card": "kKk9aW_card",
2531
- "cardOpen": "kKk9aW_cardOpen",
2532
- "chevron": "kKk9aW_chevron",
2533
- "chevronOpen": "kKk9aW_chevronOpen",
2534
- "description": "kKk9aW_description",
2535
- "discard": "kKk9aW_discard",
2536
- "failed": "kKk9aW_failed",
2537
- "field": "kKk9aW_field",
2538
- "footer": "kKk9aW_footer",
2539
- "head": "kKk9aW_head",
2540
- "headText": "kKk9aW_headText",
2541
- "header": "kKk9aW_header",
2542
- "headerStatic": "kKk9aW_headerStatic",
2543
- "hint": "kKk9aW_hint",
2544
- "input": "kKk9aW_input",
2545
- "inputInvalid": "kKk9aW_inputInvalid",
2546
- "invalid": "kKk9aW_invalid",
2547
- "label": "kKk9aW_label",
2548
- "name": "kKk9aW_name",
2549
- "notExposed": "kKk9aW_notExposed",
2550
- "pending": "kKk9aW_pending",
2551
- "readOnly": "kKk9aW_readOnly",
2552
- "reset": "kKk9aW_reset",
2553
- "save": "kKk9aW_save",
2554
- "select": "kKk9aW_select",
2555
- "selectButton": "kKk9aW_selectButton",
2556
- "selectChevron": "kKk9aW_selectChevron",
2557
- "selectChevronOpen": "kKk9aW_selectChevronOpen",
2558
- "selectLabel": "kKk9aW_selectLabel",
2559
- "selectOption": "kKk9aW_selectOption",
2560
- "selectOptionActive": "kKk9aW_selectOptionActive",
2561
- "selectOptionSelected": "kKk9aW_selectOptionSelected",
2562
- "selectPopup": "kKk9aW_selectPopup",
2563
- "selectPopupClose": "kKk9aW_selectPopupClose",
2564
- "selectPopupOpen": "kKk9aW_selectPopupOpen",
2565
- "selectWrap": "kKk9aW_selectWrap"
2725
+ "badge": "KApx_W_badge",
2726
+ "badges": "KApx_W_badges",
2727
+ "body": "KApx_W_body",
2728
+ "card": "KApx_W_card",
2729
+ "cardOpen": "KApx_W_cardOpen",
2730
+ "chevron": "KApx_W_chevron",
2731
+ "chevronOpen": "KApx_W_chevronOpen",
2732
+ "description": "KApx_W_description",
2733
+ "discard": "KApx_W_discard",
2734
+ "failed": "KApx_W_failed",
2735
+ "field": "KApx_W_field",
2736
+ "footer": "KApx_W_footer",
2737
+ "head": "KApx_W_head",
2738
+ "headText": "KApx_W_headText",
2739
+ "header": "KApx_W_header",
2740
+ "headerStatic": "KApx_W_headerStatic",
2741
+ "hint": "KApx_W_hint",
2742
+ "input": "KApx_W_input",
2743
+ "inputInvalid": "KApx_W_inputInvalid",
2744
+ "invalid": "KApx_W_invalid",
2745
+ "label": "KApx_W_label",
2746
+ "name": "KApx_W_name",
2747
+ "notExposed": "KApx_W_notExposed",
2748
+ "pending": "KApx_W_pending",
2749
+ "readOnly": "KApx_W_readOnly",
2750
+ "reset": "KApx_W_reset",
2751
+ "save": "KApx_W_save",
2752
+ "select": "KApx_W_select",
2753
+ "selectButton": "KApx_W_selectButton",
2754
+ "selectChevron": "KApx_W_selectChevron",
2755
+ "selectChevronOpen": "KApx_W_selectChevronOpen",
2756
+ "selectLabel": "KApx_W_selectLabel",
2757
+ "selectOption": "KApx_W_selectOption",
2758
+ "selectOptionActive": "KApx_W_selectOptionActive",
2759
+ "selectOptionSelected": "KApx_W_selectOptionSelected",
2760
+ "selectPopup": "KApx_W_selectPopup",
2761
+ "selectPopupClose": "KApx_W_selectPopupClose",
2762
+ "selectPopupOpen": "KApx_W_selectPopupOpen",
2763
+ "selectWrap": "KApx_W_selectWrap"
2566
2764
  };
2567
2765
  //#endregion
2568
2766
  //#region src/client/PluginSettingsCard.tsx
@@ -2646,17 +2844,20 @@ window.__ModuleLoader__.load({
2646
2844
  })
2647
2845
  ]
2648
2846
  });
2649
- if (!state.exposed) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
2650
- className: cardClass,
2651
- children: [header, expanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2652
- className: settings_card_module_css_default.body,
2653
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2654
- className: settings_card_module_css_default.notExposed,
2655
- role: "status",
2656
- children: props.t("settings.notExposed")
2657
- })
2658
- }) : null]
2659
- });
2847
+ if (!state.exposed && props.renderChildrenWhenNotExposed !== true) {
2848
+ const showNotice = props.hideNotExposedNotice !== true;
2849
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
2850
+ className: cardClass,
2851
+ children: [header, expanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2852
+ className: settings_card_module_css_default.body,
2853
+ children: showNotice ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2854
+ className: settings_card_module_css_default.notExposed,
2855
+ role: "status",
2856
+ children: props.t("settings.notExposed")
2857
+ }) : null
2858
+ }) : null]
2859
+ });
2860
+ }
2660
2861
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
2661
2862
  className: cardClass,
2662
2863
  children: [header, expanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -2803,9 +3004,11 @@ window.__ModuleLoader__.load({
2803
3004
  case " ":
2804
3005
  event.preventDefault();
2805
3006
  if (!open) openPopup();
2806
- else if (!closing) if (event.key === "ArrowDown") setActiveIndex((index) => (index + 1) % count);
2807
- else if (event.key === "ArrowUp") setActiveIndex((index) => (index - 1 + count) % count);
2808
- else commit(activeIndex);
3007
+ else if (!closing) {
3008
+ if (event.key === "ArrowDown") setActiveIndex((index) => (index + 1) % count);
3009
+ else if (event.key === "ArrowUp") setActiveIndex((index) => (index - 1 + count) % count);
3010
+ else commit(activeIndex);
3011
+ }
2809
3012
  break;
2810
3013
  case "Escape":
2811
3014
  if (open) {
@@ -2814,9 +3017,7 @@ window.__ModuleLoader__.load({
2814
3017
  close();
2815
3018
  }
2816
3019
  break;
2817
- case "Tab":
2818
- if (open) close();
2819
- break;
3020
+ case "Tab": if (open) close();
2820
3021
  }
2821
3022
  };
2822
3023
  (0, react.useEffect)(() => () => {
@@ -3074,31 +3275,31 @@ window.__ModuleLoader__.load({
3074
3275
  specs;
3075
3276
  staged = /* @__PURE__ */ new Map();
3076
3277
  listeners = /* @__PURE__ */ new Set();
3077
- /** The scope subscription installed in the constructor; released by dispose(). */
3078
- disposeScope;
3278
+ /** The form subscription installed in the constructor; released by dispose(). */
3279
+ disposeForm;
3079
3280
  disposed = false;
3080
3281
  saving = false;
3081
3282
  failed = false;
3082
3283
  failedReason;
3083
- /** @param scope - the bound settings scope for this card's namespace. */
3284
+ /** @param scope - the bound configuration form for this card's namespace. */
3084
3285
  constructor(scope, specs) {
3085
3286
  this.scope = scope;
3086
3287
  this.specs = new Map(specs.map((spec) => [spec.field, spec]));
3087
- this.disposeScope = scope.subscribe(() => {
3288
+ this.disposeForm = scope.subscribe(() => {
3088
3289
  this.publish();
3089
3290
  });
3090
3291
  }
3091
3292
  /**
3092
- * Release the scope subscription and every bound store listener. The card
3293
+ * Release the form subscription and every bound store listener. The card
3093
3294
  * must call this on teardown; later calls are no-ops.
3094
3295
  */
3095
3296
  dispose() {
3096
3297
  if (this.disposed) return;
3097
3298
  this.disposed = true;
3098
- this.disposeScope();
3299
+ this.disposeForm();
3099
3300
  this.listeners.clear();
3100
3301
  }
3101
- /** Publish a projection of this form, rebuilt whenever the scope or a draft changes. */
3302
+ /** Publish a projection of this form, rebuilt whenever the form or a draft changes. */
3102
3303
  bind(project) {
3103
3304
  const store = createSnapshotStore(project());
3104
3305
  this.listeners.add(() => {
@@ -3165,19 +3366,19 @@ window.__ModuleLoader__.load({
3165
3366
  };
3166
3367
  }
3167
3368
  /**
3168
- * Write every staged edit in one atomic scope mutation, then re-seed from
3369
+ * Write every staged edit in one atomic form mutation, then re-seed from
3169
3370
  * what the Host accepted.
3170
3371
  *
3171
3372
  * The whole batch rides one mutate, so cross-field validate hooks
3172
3373
  * (baseURL+model) judge it as a unit: the Host either applies every write
3173
- * or refuses the batch. The 0.1.2 scope contract never rejects a refused
3174
- * mutation — the scope recovers with a fresh Host view and resolves — so
3175
- * resolution alone proves nothing: the outcome is judged by reading the
3176
- * settled snapshot back, one planned write at a time, and one missed write
3177
- * fails the whole save. A scope that still rejects on refusal (the dsh-web
3178
- * bridge scope) reports through the same failure path with its rejection
3179
- * message. A save that did not land keeps its drafts, so the user can
3180
- * correct them instead of retyping.
3374
+ * or refuses the batch. The form contract answers a refusal or a skipped
3375
+ * write with `false` (it recovers with a fresh Host view instead of
3376
+ * throwing), so the outcome is judged twice: the answer itself, and then the
3377
+ * settled snapshot read back one planned write at a time. One missed write
3378
+ * fails the whole save. A transport that rejects instead (the dsh-web bridge
3379
+ * controller on a dead connection) reports through the same failure path
3380
+ * with its rejection message. A save that did not land keeps its drafts, so
3381
+ * the user can correct them instead of retyping.
3181
3382
  * @returns settlement after the mutation and the read-back.
3182
3383
  */
3183
3384
  async save() {
@@ -3199,12 +3400,13 @@ window.__ModuleLoader__.load({
3199
3400
  path: [item.field]
3200
3401
  });
3201
3402
  let failedReason;
3403
+ let accepted = false;
3202
3404
  try {
3203
- await this.scope.mutate(ops);
3405
+ accepted = await this.scope.mutate(ops);
3204
3406
  } catch (error) {
3205
3407
  failedReason = error instanceof Error ? error.message : String(error);
3206
3408
  }
3207
- const landed = failedReason === void 0 && valid.every((item) => item.judge());
3409
+ const landed = accepted && failedReason === void 0 && valid.every((item) => item.judge());
3208
3410
  for (const [field, before] of pending) if (landed && this.staged.get(field) === before) this.staged.delete(field);
3209
3411
  this.saving = false;
3210
3412
  this.failed = !landed;
@@ -3310,9 +3512,9 @@ window.__ModuleLoader__.load({
3310
3512
  }
3311
3513
  };
3312
3514
  //#endregion
3313
- //#region \0dsh-css:packages/dsh-pet/src/client/settings-section.module.css.mjs
3314
- const css = ".t0P0pa_sectionList{margin:0;padding:0;list-style:none}.t0P0pa_diagnostics{border:1px dashed var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary);border-radius:8px;margin:8px 0;padding:8px 12px;font-size:12px}.t0P0pa_diagnosticsTitle{color:var(--dsw-alias-label-primary);margin-bottom:4px;font-weight:600;display:block}.t0P0pa_diagnostics ul{margin:0;padding-left:16px}.t0P0pa_diagnostics li[data-level=error]{color:var(--dsw-alias-label-error,#c04848)}";
3315
- const tagId = "@linxin666/dsh-pet/packages/dsh-pet/src/client/settings-section.module.css";
3515
+ //#region \0dsh-css:src/client/settings-section.module.css.mjs
3516
+ const css = ".c5M8OW_sectionList{margin:0;padding:0;list-style:none}.c5M8OW_diagnostics{border:1px dashed var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary);border-radius:8px;margin:8px 0;padding:8px 12px;font-size:12px}.c5M8OW_diagnosticsTitle{color:var(--dsw-alias-label-primary);margin-bottom:4px;font-weight:600;display:block}.c5M8OW_diagnostics ul{margin:0;padding-left:16px}.c5M8OW_diagnostics li[data-level=error]{color:var(--dsw-alias-label-error,#c04848)}";
3517
+ const tagId = "@linxin666/dsh-pet/src/client/settings-section.module.css";
3316
3518
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
3317
3519
  const tag = document.createElement("style");
3318
3520
  tag.dataset.plugin = "@linxin666/dsh-pet";
@@ -3321,9 +3523,9 @@ window.__ModuleLoader__.load({
3321
3523
  document.head.appendChild(tag);
3322
3524
  }
3323
3525
  var settings_section_module_css_default = {
3324
- "diagnostics": "t0P0pa_diagnostics",
3325
- "diagnosticsTitle": "t0P0pa_diagnosticsTitle",
3326
- "sectionList": "t0P0pa_sectionList"
3526
+ "diagnostics": "c5M8OW_diagnostics",
3527
+ "diagnosticsTitle": "c5M8OW_diagnosticsTitle",
3528
+ "sectionList": "c5M8OW_sectionList"
3327
3529
  };
3328
3530
  //#endregion
3329
3531
  //#region src/client/PetSettingsCard.tsx
@@ -3339,6 +3541,14 @@ window.__ModuleLoader__.load({
3339
3541
  if (!response.ok) throw new Error("pet diagnostics failed: " + response.status);
3340
3542
  return (await response.json()).diagnostics ?? [];
3341
3543
  }
3544
+ /** Read the selection from the same persisted state the pet renders. */
3545
+ async function fetchSelectedPetId() {
3546
+ const response = await fetch("/api/pet/state");
3547
+ if (!response.ok) throw new Error("pet state failed: " + response.status);
3548
+ const body = await response.json();
3549
+ if (typeof body.pet?.id !== "string") throw new Error("pet state has no selected pet");
3550
+ return body.pet.id;
3551
+ }
3342
3552
  /** Bridges the 'pet' scope onto the card's staged form. */
3343
3553
  var PetSettingsCardController = class {
3344
3554
  form;
@@ -3346,12 +3556,16 @@ window.__ModuleLoader__.load({
3346
3556
  petChoices = [];
3347
3557
  petLabels = /* @__PURE__ */ new Map();
3348
3558
  diagnostics = [];
3559
+ selectedPetId;
3560
+ stagedPetId;
3561
+ savingPet = false;
3562
+ petSaveFailed = false;
3349
3563
  loaded = false;
3350
3564
  attempts = 0;
3351
3565
  disposed = false;
3352
3566
  /** Pending deferred-load or retry timer; cancelled by dispose(). */
3353
3567
  pendingTimer;
3354
- /** @param scope - the bound settings scope for the 'pet' namespace. */
3568
+ /** @param scope - the bound configuration form for the 'pet' entry. */
3355
3569
  constructor(scope) {
3356
3570
  this.form = new CardForm(scope, [
3357
3571
  booleanField("enabled"),
@@ -3369,6 +3583,7 @@ window.__ModuleLoader__.load({
3369
3583
  if (this.disposed) return;
3370
3584
  this.loadPets();
3371
3585
  this.loadDiagnostics();
3586
+ this.loadSelectedPet();
3372
3587
  }, 0);
3373
3588
  }
3374
3589
  /** Fetch registry diagnostics once (soft-fail: an empty list on error). */
@@ -3401,9 +3616,57 @@ window.__ModuleLoader__.load({
3401
3616
  }, 3e3);
3402
3617
  }
3403
3618
  }
3619
+ async loadSelectedPet() {
3620
+ try {
3621
+ const petId = await fetchSelectedPetId();
3622
+ if (this.disposed) return;
3623
+ this.selectedPetId = petId;
3624
+ this.store.set(this.projection());
3625
+ } catch {}
3626
+ }
3627
+ fallback() {
3628
+ const shell = this.form.shell();
3629
+ return shell.available && !shell.exposed && this.selectedPetId !== void 0;
3630
+ }
3631
+ async savePetSelection() {
3632
+ const petId = this.stagedPetId;
3633
+ if (petId === void 0 || this.savingPet || !this.petChoices.includes(petId)) return;
3634
+ this.savingPet = true;
3635
+ this.petSaveFailed = false;
3636
+ this.store.set(this.projection());
3637
+ try {
3638
+ const response = await fetch("/api/pet/set-pet", {
3639
+ method: "POST",
3640
+ headers: { "content-type": "application/json" },
3641
+ body: JSON.stringify({ petId })
3642
+ });
3643
+ const result = await response.json();
3644
+ if (!response.ok || result.ok !== true || result.petId !== petId || await fetchSelectedPetId() !== petId) throw new Error("pet selection was not persisted");
3645
+ this.selectedPetId = petId;
3646
+ if (this.stagedPetId === petId) this.stagedPetId = void 0;
3647
+ } catch {
3648
+ this.petSaveFailed = true;
3649
+ } finally {
3650
+ this.savingPet = false;
3651
+ if (!this.disposed) this.store.set(this.projection());
3652
+ }
3653
+ }
3404
3654
  projection() {
3655
+ const fallback = this.fallback();
3656
+ const shell = this.form.shell();
3657
+ const configuredPet = this.form.field("petId");
3405
3658
  return {
3406
- ...this.form.shell(),
3659
+ ...shell,
3660
+ ...fallback ? {
3661
+ exposed: true,
3662
+ writable: true,
3663
+ dirty: this.stagedPetId !== void 0 && this.stagedPetId !== this.selectedPetId,
3664
+ invalid: this.stagedPetId !== void 0 && !this.petChoices.includes(this.stagedPetId),
3665
+ saving: this.savingPet,
3666
+ failed: this.petSaveFailed,
3667
+ failedReason: void 0
3668
+ } : {},
3669
+ petSelectionFallback: fallback,
3407
3670
  enabled: this.form.field("enabled"),
3408
3671
  decorationEnabled: this.form.field("decorationEnabled"),
3409
3672
  visible: this.form.field("visible"),
@@ -3411,7 +3674,14 @@ window.__ModuleLoader__.load({
3411
3674
  right: this.form.field("right"),
3412
3675
  bottom: this.form.field("bottom"),
3413
3676
  bubbleScale: this.form.field("bubbleScale"),
3414
- petId: this.form.field("petId"),
3677
+ petId: fallback ? {
3678
+ text: this.stagedPetId ?? this.selectedPetId ?? "",
3679
+ overridden: false,
3680
+ invalid: this.stagedPetId !== void 0 && !this.petChoices.includes(this.stagedPetId)
3681
+ } : configuredPet.text === "" && this.selectedPetId !== void 0 ? {
3682
+ ...configuredPet,
3683
+ text: this.selectedPetId
3684
+ } : configuredPet,
3415
3685
  petChoices: this.petChoices.map((id) => ({
3416
3686
  value: id,
3417
3687
  label: this.petLabels.get(id) ?? id
@@ -3424,9 +3694,33 @@ window.__ModuleLoader__.load({
3424
3694
  * @returns the card's snapshot and its form actions.
3425
3695
  */
3426
3696
  inject() {
3697
+ const actions = this.form.actions();
3427
3698
  return {
3428
3699
  hooks: { petSettingsCard: this.store },
3429
- ...this.form.actions()
3700
+ edit: (field, value) => {
3701
+ if (!this.fallback()) return actions.edit(field, value);
3702
+ if (field !== "petId") return;
3703
+ this.stagedPetId = value === "" ? void 0 : value;
3704
+ this.petSaveFailed = false;
3705
+ this.store.set(this.projection());
3706
+ },
3707
+ resetField: (field) => {
3708
+ if (!this.fallback()) return actions.resetField(field);
3709
+ if (field !== "petId") return;
3710
+ this.stagedPetId = void 0;
3711
+ this.petSaveFailed = false;
3712
+ this.store.set(this.projection());
3713
+ },
3714
+ save: () => {
3715
+ if (this.fallback()) this.savePetSelection();
3716
+ else actions.save();
3717
+ },
3718
+ discard: () => {
3719
+ if (!this.fallback()) return actions.discard();
3720
+ this.stagedPetId = void 0;
3721
+ this.petSaveFailed = false;
3722
+ this.store.set(this.projection());
3723
+ }
3430
3724
  };
3431
3725
  }
3432
3726
  /**
@@ -3462,12 +3756,13 @@ window.__ModuleLoader__.load({
3462
3756
  t,
3463
3757
  titleKey: "settings.title",
3464
3758
  descriptionKey: "settings.description",
3759
+ descriptionNode: state.petSelectionFallback ? t("settings.petHint") : void 0,
3465
3760
  state,
3466
3761
  onSave: props.save,
3467
3762
  onDiscard: props.discard,
3468
3763
  alwaysOpen: true,
3469
3764
  children: [
3470
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BooleanField, {
3765
+ state.petSelectionFallback ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BooleanField, {
3471
3766
  id: "settings-pet-enabled",
3472
3767
  label: t("settings.enabled"),
3473
3768
  hint: t("settings.enabledHint"),
@@ -3483,7 +3778,7 @@ window.__ModuleLoader__.load({
3483
3778
  props.resetField("enabled");
3484
3779
  }
3485
3780
  }),
3486
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BooleanField, {
3781
+ state.petSelectionFallback ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BooleanField, {
3487
3782
  id: "settings-pet-decoration",
3488
3783
  label: t("settings.decoration"),
3489
3784
  hint: t("settings.decorationHint"),
@@ -3525,7 +3820,7 @@ window.__ModuleLoader__.load({
3525
3820
  children: diagnostic.message
3526
3821
  }, index)) })]
3527
3822
  }),
3528
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BooleanField, {
3823
+ state.petSelectionFallback ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BooleanField, {
3529
3824
  id: "settings-pet-visible",
3530
3825
  label: t("settings.visible"),
3531
3826
  hint: t("settings.visibleHint"),
@@ -3541,7 +3836,7 @@ window.__ModuleLoader__.load({
3541
3836
  props.resetField("visible");
3542
3837
  }
3543
3838
  }),
3544
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ValueField, {
3839
+ state.petSelectionFallback ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ValueField, {
3545
3840
  id: "settings-pet-size",
3546
3841
  label: t("settings.size"),
3547
3842
  hint: t("settings.sizeHint"),
@@ -3555,7 +3850,7 @@ window.__ModuleLoader__.load({
3555
3850
  props.resetField("size");
3556
3851
  }
3557
3852
  }),
3558
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ValueField, {
3853
+ state.petSelectionFallback ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ValueField, {
3559
3854
  id: "settings-pet-right",
3560
3855
  label: t("settings.right"),
3561
3856
  hint: t("settings.rightHint"),
@@ -3569,7 +3864,7 @@ window.__ModuleLoader__.load({
3569
3864
  props.resetField("right");
3570
3865
  }
3571
3866
  }),
3572
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ValueField, {
3867
+ state.petSelectionFallback ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ValueField, {
3573
3868
  id: "settings-pet-bottom",
3574
3869
  label: t("settings.bottom"),
3575
3870
  hint: t("settings.bottomHint"),
@@ -3583,7 +3878,7 @@ window.__ModuleLoader__.load({
3583
3878
  props.resetField("bottom");
3584
3879
  }
3585
3880
  }),
3586
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ValueField, {
3881
+ state.petSelectionFallback ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ValueField, {
3587
3882
  id: "settings-pet-bubble-scale",
3588
3883
  label: t("settings.bubbleScale"),
3589
3884
  hint: t("settings.bubbleScaleHint"),
@@ -3806,6 +4101,21 @@ window.__ModuleLoader__.load({
3806
4101
  return text;
3807
4102
  }
3808
4103
  //#endregion
4104
+ //#region src/client/main-session.ts
4105
+ /**
4106
+ * Resolve the Session the main view currently shows.
4107
+ *
4108
+ * Reads the catalog's rows rather than a per-id retain-info source: ownership
4109
+ * counts ride the list snapshot, so this neither allocates observers nor opens
4110
+ * history, and a subscription to the list still fires when the selection moves.
4111
+ * @param byId - the session catalog's rows (`SessionListState.byId`), when available.
4112
+ * @returns the main-view session id, or undefined when the main view shows none.
4113
+ */
4114
+ function mainViewSessionId(byId) {
4115
+ if (byId === void 0 || byId === null) return void 0;
4116
+ for (const row of Object.values(byId)) if (row !== void 0 && (row.retainedBy?.mainView ?? 0) > 0) return row.id;
4117
+ }
4118
+ //#endregion
3809
4119
  //#region src/client/telemetry.ts
3810
4120
  const VISITOR_KEY = "dsh-web-ui-telemetry-visitor";
3811
4121
  const DAY_KEY_PREFIX = "dsh-web-ui-telemetry-day:";
@@ -3813,7 +4123,7 @@ window.__ModuleLoader__.load({
3813
4123
  /** The building package's version, when the bundle carries it. */
3814
4124
  function bakedVersion() {
3815
4125
  try {
3816
- return "0.3.24";
4126
+ return "0.4.2";
3817
4127
  } catch {
3818
4128
  return;
3819
4129
  }
@@ -3904,17 +4214,38 @@ window.__ModuleLoader__.load({
3904
4214
  };
3905
4215
  /** Poll interval for the host snapshot. */
3906
4216
  const POLL_MS = 2e3;
3907
- /** Settings namespace the pet settings card edits (the Host plugin registers it). */
4217
+ /** Settings namespace of the pet's settings page. It is the profile entry id the Host serves a form for. */
3908
4218
  const PET_SETTINGS_NS = "pet";
3909
4219
  /** Required services (sessions powers bubble-to-session navigation). */
3910
4220
  const inject = [
3911
4221
  "slots",
3912
4222
  "locale",
3913
4223
  "connection",
3914
- "settingsScope",
4224
+ "configForms",
3915
4225
  "remote",
3916
- "sessions"
4226
+ "sessions",
4227
+ "uiWorkspace"
3917
4228
  ];
4229
+ const PET_ENTRY_IDS = [
4230
+ "web-ui-pet",
4231
+ "ui-pet",
4232
+ PET_SETTINGS_NS
4233
+ ];
4234
+ function servedEntryId(forms) {
4235
+ let served;
4236
+ try {
4237
+ served = forms.describe().getSnapshot().view?.namespaces.map((view) => view.ns);
4238
+ } catch {
4239
+ served = void 0;
4240
+ }
4241
+ if (!served || served.length === 0) return PET_SETTINGS_NS;
4242
+ return PET_ENTRY_IDS.find((id) => served.includes(id)) ?? PET_SETTINGS_NS;
4243
+ }
4244
+ function petSettingsForm(ctx) {
4245
+ const binder = ctx.get("webUiSettings");
4246
+ if (binder !== void 0 && typeof binder.bind === "function") return binder.bind({ namespace: PET_SETTINGS_NS });
4247
+ return ctx.configForms.get(servedEntryId(ctx.configForms));
4248
+ }
3918
4249
  /**
3919
4250
  * Client plugin body: register dictionaries, mount the global pet entry and
3920
4251
  * poll loop while the plugin is enabled, and seat the settings card as a
@@ -3946,12 +4277,12 @@ window.__ModuleLoader__.load({
3946
4277
  }, "pet: dictionaries");
3947
4278
  defaultPetRendererRegistry.register(live2dRenderer);
3948
4279
  defaultPetRendererRegistry.register(frames2dRenderer);
3949
- const settingsScope = (ctx.get("webUiSettings") ?? ctx.settingsScope).bind({ namespace: PET_SETTINGS_NS });
4280
+ const settingsForm = petSettingsForm(ctx);
3950
4281
  const enabled = () => {
3951
- const snapshot = settingsScope.getSnapshot();
4282
+ const snapshot = settingsForm.getSnapshot();
3952
4283
  return snapshot.status === "ready" ? snapshot.value?.enabled ?? true : snapshot.status === "unavailable";
3953
4284
  };
3954
- const petSettings = new PetSettingsCardController(settingsScope);
4285
+ const petSettings = new PetSettingsCardController(settingsForm);
3955
4286
  ctx.slots.inject("settings.section", () => {
3956
4287
  try {
3957
4288
  const unregister = ctx.slots.register({
@@ -3989,10 +4320,7 @@ window.__ModuleLoader__.load({
3989
4320
  const setState = petStore.actions.setState;
3990
4321
  const setFeedback = petStore.actions.setFeedback;
3991
4322
  const sessions = ctx.sessions;
3992
- const currentSessionId = () => {
3993
- const current = sessions.list.getSnapshot().current;
3994
- return current === void 0 ? void 0 : String(current);
3995
- };
4323
+ const currentSessionId = () => mainViewSessionId(sessions.list.getSnapshot().byId);
3996
4324
  let petsLoaded = false;
3997
4325
  let stateSeq = 0;
3998
4326
  const pollNow = () => {
@@ -4041,7 +4369,7 @@ window.__ModuleLoader__.load({
4041
4369
  }, "pet: current-session watch");
4042
4370
  const openSession = (sessionId) => {
4043
4371
  if (sessions.list.getSnapshot().byId[sessionId] === void 0) return;
4044
- sessions.open(sessionId);
4372
+ ctx.uiWorkspace.openSession(sessionId);
4045
4373
  };
4046
4374
  const injected = () => ({
4047
4375
  store: petStore,
@@ -4144,7 +4472,7 @@ window.__ModuleLoader__.load({
4144
4472
  disposeUi = void 0;
4145
4473
  }
4146
4474
  };
4147
- const unsubscribeSettings = settingsScope.subscribe(syncUi);
4475
+ const unsubscribeSettings = settingsForm.subscribe(syncUi);
4148
4476
  ctx.effect(() => () => {
4149
4477
  unsubscribeSettings();
4150
4478
  killUi();