@linxin666/dsh-pet 0.3.5 → 0.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +4 -0
- package/README.zh.md +4 -0
- package/contracts/voice-pack-v1.schema.json +30 -0
- package/cordis.patch.yml +1 -1
- package/lib/client.js +330 -103
- package/lib/client.js.map +1 -1
- package/lib/index.js +145 -28
- package/lib/invariant.js +1 -1
- package/lib/live2d-vendor.js +106 -95
- package/lib/live2d-vendor.js.map +1 -1
- package/lib/{state-DrMX22GL.js → state-NvDEoCln.js} +5 -2
- package/lib/types/access.d.ts +5 -4
- package/lib/types/access.d.ts.map +1 -1
- package/lib/types/access.js +2 -12
- package/lib/types/affinity.d.ts +2 -0
- package/lib/types/affinity.d.ts.map +1 -1
- package/lib/types/affinity.js +12 -0
- package/lib/types/announce.d.ts +53 -0
- package/lib/types/announce.d.ts.map +1 -0
- package/lib/types/announce.js +80 -0
- package/lib/types/client/PetDockEntry.d.ts +14 -5
- package/lib/types/client/PetDockEntry.d.ts.map +1 -1
- package/lib/types/client/PetDockEntry.js +6 -5
- package/lib/types/client/PetSettingsCard.d.ts +2 -1
- package/lib/types/client/PetSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PetSprite.d.ts +8 -0
- package/lib/types/client/PetSprite.d.ts.map +1 -1
- package/lib/types/client/PetSprite.js +36 -5
- package/lib/types/client/index.d.ts +8 -5
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +11 -5
- package/lib/types/client/locales.d.ts +20 -2
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +20 -2
- package/lib/types/client/pet-store.d.ts +1 -1
- package/lib/types/client/pet-store.d.ts.map +1 -1
- package/lib/types/client/pet-store.js +1 -1
- package/lib/types/client/renderers/frames2d.d.ts +12 -0
- package/lib/types/client/renderers/frames2d.d.ts.map +1 -1
- package/lib/types/client/renderers/frames2d.js +143 -18
- package/lib/types/client/settings-form.d.ts +35 -43
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/settings-form.js +60 -53
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +10 -9
- package/lib/types/ledger.d.ts +4 -2
- package/lib/types/ledger.d.ts.map +1 -1
- package/lib/types/ledger.js +4 -4
- package/lib/types/pair-access.d.ts +35 -0
- package/lib/types/pair-access.d.ts.map +1 -0
- package/lib/types/pair-access.js +19 -0
- package/lib/types/remarks.d.ts +1 -1
- package/lib/types/remarks.d.ts.map +1 -1
- package/lib/types/remarks.js +11 -2
- package/lib/types/service.d.ts +18 -0
- package/lib/types/service.d.ts.map +1 -1
- package/lib/types/service.js +26 -1
- package/lib/types/voice-pack.d.ts +5 -0
- package/lib/types/voice-pack.d.ts.map +1 -1
- package/lib/types/voice-pack.js +36 -2
- package/package.json +17 -13
- package/src/access.ts +7 -27
- package/src/affinity.ts +13 -0
- package/src/announce.ts +103 -0
- package/src/client/PetDockEntry.test.tsx +1 -1
- package/src/client/PetDockEntry.tsx +15 -4
- package/src/client/PetSettingsCard.tsx +2 -1
- package/src/client/PetSprite.test.tsx +36 -0
- package/src/client/PetSprite.tsx +75 -4
- package/src/client/gameplay-hud.test.tsx +1 -1
- package/src/client/index.test.tsx +2 -2
- package/src/client/index.ts +17 -6
- package/src/client/locales.ts +20 -2
- package/src/client/pet-store.ts +2 -2
- package/src/client/pet.module.css +104 -0
- package/src/client/renderers/frames2d.test.ts +101 -0
- package/src/client/renderers/frames2d.ts +138 -18
- package/src/client/settings-form.ts +78 -92
- package/src/dsh-home.ts +1 -1
- package/src/index.ts +17 -15
- package/src/ledger.ts +6 -4
- package/src/pair-access.ts +49 -0
- package/src/remarks.test.ts +10 -0
- package/src/remarks.ts +9 -2
- package/src/service.ts +32 -2
- package/src/voice-pack.test.ts +39 -0
- package/src/voice-pack.ts +37 -2
package/lib/client.js
CHANGED
|
@@ -6,9 +6,21 @@ window.__ModuleLoader__.load({
|
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let react = require("react");
|
|
8
8
|
let react_dom_client = require("react-dom/client");
|
|
9
|
-
let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
|
|
10
9
|
let react_dom = require("react-dom");
|
|
11
10
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
11
|
+
//#region \0dsh-store-engine
|
|
12
|
+
const platform = ["@deepseek-ai/dsh-client", "-store"].join("");
|
|
13
|
+
const legacy = ["@deepseek-ai/dsh-client-runtime", "/client"].join("");
|
|
14
|
+
let engine;
|
|
15
|
+
try {
|
|
16
|
+
engine = require(platform);
|
|
17
|
+
} catch {
|
|
18
|
+
engine = require(legacy);
|
|
19
|
+
}
|
|
20
|
+
const createSnapshotStore = engine.createSnapshotStore;
|
|
21
|
+
const defineStore = engine.defineStore;
|
|
22
|
+
engine.shallowEqual;
|
|
23
|
+
//#endregion
|
|
12
24
|
//#region src/client/pet-store.ts
|
|
13
25
|
/**
|
|
14
26
|
* Browser-side pet store: the pet state snapshot plus transient UI feedback
|
|
@@ -19,7 +31,7 @@ window.__ModuleLoader__.load({
|
|
|
19
31
|
*/
|
|
20
32
|
/** Create the pet store handle (apply world only; never module-level). */
|
|
21
33
|
function createPetStore() {
|
|
22
|
-
return
|
|
34
|
+
return defineStore({
|
|
23
35
|
init: () => ({
|
|
24
36
|
snapshot: null,
|
|
25
37
|
pets: [],
|
|
@@ -68,6 +80,12 @@ window.__ModuleLoader__.load({
|
|
|
68
80
|
return n;
|
|
69
81
|
}
|
|
70
82
|
//#endregion
|
|
83
|
+
//#region src/announce.ts
|
|
84
|
+
/** Whether an announcement is still fresh at `now`. */
|
|
85
|
+
function announcementFresh(announcement, now) {
|
|
86
|
+
return now - announcement.at < announcement.ttlMs;
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
71
89
|
//#region src/state.ts
|
|
72
90
|
/**
|
|
73
91
|
* Map one activity phase onto the animation contract.
|
|
@@ -171,7 +189,7 @@ window.__ModuleLoader__.load({
|
|
|
171
189
|
}
|
|
172
190
|
//#endregion
|
|
173
191
|
//#region \0dsh-css:packages/dsh-pet/src/client/pet.module.css.mjs
|
|
174
|
-
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_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}}";
|
|
192
|
+
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_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}}";
|
|
175
193
|
const tagId$2 = "@linxin666/dsh-pet/pet.module.css";
|
|
176
194
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
|
|
177
195
|
const tag = document.createElement("style");
|
|
@@ -191,6 +209,16 @@ window.__ModuleLoader__.load({
|
|
|
191
209
|
"bubblePet": "kz2Bea_bubblePet",
|
|
192
210
|
"bubbleStack": "kz2Bea_bubbleStack",
|
|
193
211
|
"bubbleStatus": "kz2Bea_bubbleStatus",
|
|
212
|
+
"bubbleUsage": "kz2Bea_bubbleUsage",
|
|
213
|
+
"bubbleUsageHead": "kz2Bea_bubbleUsageHead",
|
|
214
|
+
"bubbleUsageLow": "kz2Bea_bubbleUsageLow",
|
|
215
|
+
"bubbleUsageMeter": "kz2Bea_bubbleUsageMeter",
|
|
216
|
+
"bubbleUsageMeterFill": "kz2Bea_bubbleUsageMeterFill",
|
|
217
|
+
"bubbleUsageNote": "kz2Bea_bubbleUsageNote",
|
|
218
|
+
"bubbleUsageOk": "kz2Bea_bubbleUsageOk",
|
|
219
|
+
"bubbleUsageTitle": "kz2Bea_bubbleUsageTitle",
|
|
220
|
+
"bubbleUsageValue": "kz2Bea_bubbleUsageValue",
|
|
221
|
+
"bubbleUsageWarn": "kz2Bea_bubbleUsageWarn",
|
|
194
222
|
"bubbleWhisper": "kz2Bea_bubbleWhisper",
|
|
195
223
|
"float": "kz2Bea_float",
|
|
196
224
|
"gameplayActions": "kz2Bea_gameplayActions",
|
|
@@ -217,6 +245,7 @@ window.__ModuleLoader__.load({
|
|
|
217
245
|
"pet-bubble-pop": "kz2Bea_pet-bubble-pop",
|
|
218
246
|
"pet-gameplay-float": "kz2Bea_pet-gameplay-float",
|
|
219
247
|
"pet-panel-in": "kz2Bea_pet-panel-in",
|
|
248
|
+
"pet-usage-in": "kz2Bea_pet-usage-in",
|
|
220
249
|
"pet-whisper-in": "kz2Bea_pet-whisper-in",
|
|
221
250
|
"rankRow": "kz2Bea_rankRow",
|
|
222
251
|
"renameRow": "kz2Bea_renameRow",
|
|
@@ -319,6 +348,54 @@ window.__ModuleLoader__.load({
|
|
|
319
348
|
});
|
|
320
349
|
}
|
|
321
350
|
/**
|
|
351
|
+
* The announcement bubble (dsh-usage linkage): a dedicated, specially
|
|
352
|
+
* designed surface for sibling-plugin facts — a balance or today-spend pill,
|
|
353
|
+
* or a plan-quota card with a tone-tinted accent, a mini meter for percent
|
|
354
|
+
* windows, and the reset instant. It rides the top of the session bubble
|
|
355
|
+
* stack (column-reverse puts the DOM-last child farthest from the sprite)
|
|
356
|
+
* and persists for its TTL instead of the short feedback pop.
|
|
357
|
+
*/
|
|
358
|
+
function UsageAnnouncementBubble(props) {
|
|
359
|
+
const { announcement } = props;
|
|
360
|
+
const tone = announcement.tone === "low" ? pet_module_css_default.bubbleUsageLow : announcement.tone === "warn" ? pet_module_css_default.bubbleUsageWarn : pet_module_css_default.bubbleUsageOk;
|
|
361
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
362
|
+
className: clsx(pet_module_css_default.bubble, pet_module_css_default.bubbleUsage, tone),
|
|
363
|
+
role: "status",
|
|
364
|
+
"aria-live": "polite",
|
|
365
|
+
"data-dsh-pet-announcement": announcement.source,
|
|
366
|
+
children: [
|
|
367
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
368
|
+
className: pet_module_css_default.bubbleUsageHead,
|
|
369
|
+
children: [
|
|
370
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
371
|
+
className: pet_module_css_default.bubbleUsageTitle,
|
|
372
|
+
children: announcement.title
|
|
373
|
+
}),
|
|
374
|
+
(announcement.kind === "balance" || announcement.kind === "cost") && announcement.amount !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
375
|
+
className: pet_module_css_default.bubbleUsageValue,
|
|
376
|
+
children: announcement.amount
|
|
377
|
+
}),
|
|
378
|
+
announcement.kind === "plan" && announcement.percent !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
379
|
+
className: pet_module_css_default.bubbleUsageValue,
|
|
380
|
+
children: Math.round(announcement.percent) + "%"
|
|
381
|
+
})
|
|
382
|
+
]
|
|
383
|
+
}),
|
|
384
|
+
announcement.kind === "plan" && announcement.percent !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
385
|
+
className: pet_module_css_default.bubbleUsageMeter,
|
|
386
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
387
|
+
className: pet_module_css_default.bubbleUsageMeterFill,
|
|
388
|
+
style: { width: Math.min(100, Math.max(0, announcement.percent)) + "%" }
|
|
389
|
+
})
|
|
390
|
+
}),
|
|
391
|
+
announcement.note !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
392
|
+
className: pet_module_css_default.bubbleUsageNote,
|
|
393
|
+
children: announcement.note
|
|
394
|
+
})
|
|
395
|
+
]
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
322
399
|
* The floating pet. The spritesheet frame advances on requestAnimationFrame
|
|
323
400
|
* with per-frame durations from the definition's tracks; the atlas image is
|
|
324
401
|
* loaded once and the background position is written straight to the sprite
|
|
@@ -517,7 +594,9 @@ window.__ModuleLoader__.load({
|
|
|
517
594
|
const stackOpen = stackPeek || stackPinned;
|
|
518
595
|
const visibleSessions = !stackOpen && sessionBubbles.length > 1 ? sessionBubbles.slice(0, 1) : sessionBubbles;
|
|
519
596
|
const statusBubble = feedback === null && sessionBubbles.length === 0 ? snapshot?.bubble : void 0;
|
|
520
|
-
const
|
|
597
|
+
const announcement = snapshot?.announcement;
|
|
598
|
+
const usageAnnouncement = feedback === null && announcement !== void 0 && announcementFresh(announcement, Date.now()) ? announcement : void 0;
|
|
599
|
+
const bubblePresent = feedback !== null || sessionBubbles.length > 0 || statusBubble !== void 0 || usageAnnouncement !== void 0;
|
|
521
600
|
const displayName = snapshot?.name ?? definition.displayName;
|
|
522
601
|
const decoration = snapshot?.decoration;
|
|
523
602
|
(0, react.useEffect)(() => {
|
|
@@ -614,50 +693,54 @@ window.__ModuleLoader__.load({
|
|
|
614
693
|
className: clsx(pet_module_css_default.bubble, feedback.kind === "feed" ? pet_module_css_default.bubbleFeed : pet_module_css_default.bubblePet),
|
|
615
694
|
children: feedback.text
|
|
616
695
|
}, feedback.at),
|
|
617
|
-
feedback === null && (sessionBubbles.length > 0 || statusBubble !== void 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
696
|
+
feedback === null && (sessionBubbles.length > 0 || statusBubble !== void 0 || usageAnnouncement !== void 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
618
697
|
ref: bubbleRef,
|
|
619
698
|
className: pet_module_css_default.bubbleStack,
|
|
620
699
|
onPointerEnter: () => setStackPeek(true),
|
|
621
700
|
onPointerLeave: () => setStackPeek(false),
|
|
622
|
-
children: [
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
className: clsx(pet_module_css_default.bubble, pet_module_css_default.bubbleStatus, pet_module_css_default.bubbleClickable, speaksWhisper && pet_module_css_default.bubbleWhisper),
|
|
627
|
-
title: props.t("pet.openSessionHint"),
|
|
628
|
-
onClick: () => {
|
|
629
|
-
props.onOpenSession(session.sessionId);
|
|
630
|
-
},
|
|
631
|
-
children: [index === 0 && !speaksWhisper && decoration !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusOrnament, {
|
|
632
|
-
decoration,
|
|
633
|
-
phase
|
|
634
|
-
}), session.whisper ?? session.bubble]
|
|
635
|
-
}, speaksWhisper ? "whisper:" + session.whisper : session.sessionId);
|
|
636
|
-
if (index !== 0 || sessionBubbles.length <= 1) return bubble;
|
|
637
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
638
|
-
className: pet_module_css_default.bubbleAnchor,
|
|
639
|
-
children: [bubble, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
701
|
+
children: [
|
|
702
|
+
visibleSessions.map((session, index) => {
|
|
703
|
+
const speaksWhisper = session.whisper !== void 0;
|
|
704
|
+
const bubble = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
640
705
|
type: "button",
|
|
641
|
-
className: pet_module_css_default.
|
|
642
|
-
title:
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
onClick: (e) => {
|
|
646
|
-
e.stopPropagation();
|
|
647
|
-
setStackPinned((open) => !open);
|
|
706
|
+
className: clsx(pet_module_css_default.bubble, pet_module_css_default.bubbleStatus, pet_module_css_default.bubbleClickable, speaksWhisper && pet_module_css_default.bubbleWhisper),
|
|
707
|
+
title: props.t("pet.openSessionHint"),
|
|
708
|
+
onClick: () => {
|
|
709
|
+
props.onOpenSession(session.sessionId);
|
|
648
710
|
},
|
|
649
|
-
children:
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
711
|
+
children: [index === 0 && !speaksWhisper && decoration !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusOrnament, {
|
|
712
|
+
decoration,
|
|
713
|
+
phase
|
|
714
|
+
}), session.whisper ?? session.bubble]
|
|
715
|
+
}, speaksWhisper ? "whisper:" + session.whisper : session.sessionId);
|
|
716
|
+
if (index !== 0 || sessionBubbles.length <= 1) return bubble;
|
|
717
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
718
|
+
className: pet_module_css_default.bubbleAnchor,
|
|
719
|
+
children: [bubble, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
720
|
+
type: "button",
|
|
721
|
+
className: pet_module_css_default.bubbleMore,
|
|
722
|
+
title: stackOpen ? props.t("pet.collapseSessions") : props.t("pet.moreSessions", { n: sessionBubbles.length - 1 }),
|
|
723
|
+
"aria-label": stackOpen ? props.t("pet.collapseSessions") : props.t("pet.moreSessions", { n: sessionBubbles.length - 1 }),
|
|
724
|
+
"aria-expanded": stackOpen,
|
|
725
|
+
onClick: (e) => {
|
|
726
|
+
e.stopPropagation();
|
|
727
|
+
setStackPinned((open) => !open);
|
|
728
|
+
},
|
|
729
|
+
children: stackOpen ? "×" : "+" + String(sessionBubbles.length - 1)
|
|
730
|
+
})]
|
|
731
|
+
}, "primary");
|
|
732
|
+
}),
|
|
733
|
+
sessionBubbles.length === 0 && statusBubble !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
734
|
+
className: clsx(pet_module_css_default.bubble, pet_module_css_default.bubbleStatus),
|
|
735
|
+
role: "status",
|
|
736
|
+
"aria-live": "polite",
|
|
737
|
+
children: [decoration !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusOrnament, {
|
|
738
|
+
decoration,
|
|
739
|
+
phase
|
|
740
|
+
}), statusBubble]
|
|
741
|
+
}, "status"),
|
|
742
|
+
usageAnnouncement !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageAnnouncementBubble, { announcement: usageAnnouncement })
|
|
743
|
+
]
|
|
661
744
|
}),
|
|
662
745
|
hovered && dragRef.current === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
663
746
|
ref: panelRef,
|
|
@@ -712,7 +795,12 @@ window.__ModuleLoader__.load({
|
|
|
712
795
|
children: displayName
|
|
713
796
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
714
797
|
className: pet_module_css_default.statRank,
|
|
715
|
-
children:
|
|
798
|
+
children: (() => {
|
|
799
|
+
const rawRank = snapshot?.affinity.rank ?? "?";
|
|
800
|
+
const rankKey = `pet.rank.name.${rawRank}`;
|
|
801
|
+
const localized = props.t(rankKey);
|
|
802
|
+
return panelStat("rank", "pet.rank", { rank: localized !== rankKey ? localized : rawRank });
|
|
803
|
+
})()
|
|
716
804
|
})]
|
|
717
805
|
}),
|
|
718
806
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -761,7 +849,7 @@ window.__ModuleLoader__.load({
|
|
|
761
849
|
] })
|
|
762
850
|
})
|
|
763
851
|
]
|
|
764
|
-
}), document.body);
|
|
852
|
+
}), props.portalTarget ?? document.body);
|
|
765
853
|
}
|
|
766
854
|
//#endregion
|
|
767
855
|
//#region src/client/drag-stream.ts
|
|
@@ -1431,10 +1519,11 @@ window.__ModuleLoader__.load({
|
|
|
1431
1519
|
* it must not ride a session-scoped slot — on the new-conversation screen no
|
|
1432
1520
|
* session exists to scope a slot by, and the pet would vanish (issue #48).
|
|
1433
1521
|
* The client half therefore mounts this entry straight onto 'document.body'
|
|
1434
|
-
* (see index.ts): while visible it renders the floating PetSprite (a
|
|
1435
|
-
*
|
|
1436
|
-
*
|
|
1437
|
-
* the registry list — no per-pet
|
|
1522
|
+
* (see index.ts): while visible it renders the floating PetSprite (a portal
|
|
1523
|
+
* into the plugin root, so the root owns the whole surface), while hidden it
|
|
1524
|
+
* renders a fixed-position summon button. Which sprite renders is decided by
|
|
1525
|
+
* the host snapshot's pet id resolved against the registry list — no per-pet
|
|
1526
|
+
* component exists.
|
|
1438
1527
|
* @module @linxin666/dsh-pet/client/PetDockEntry
|
|
1439
1528
|
*/
|
|
1440
1529
|
const DEFAULT_DISPLAY = {
|
|
@@ -1492,6 +1581,7 @@ window.__ModuleLoader__.load({
|
|
|
1492
1581
|
onRename: props.rename,
|
|
1493
1582
|
onOpenSession: props.openSession,
|
|
1494
1583
|
onFeedbackDone: props.feedbackDone,
|
|
1584
|
+
portalTarget: props.portalTarget,
|
|
1495
1585
|
dragDisabled: snapshot.gameplay?.mode === "work",
|
|
1496
1586
|
...gameplay === void 0 || aux === null ? {} : {
|
|
1497
1587
|
onGameplayTap: (fx, fy) => aux.bus.tap?.(fx, fy),
|
|
@@ -1811,6 +1901,18 @@ window.__ModuleLoader__.load({
|
|
|
1811
1901
|
* phase-mapped track. Rendering never throws: a broken track list degrades
|
|
1812
1902
|
* to the first decodable frame, and the 1.2 s stall watchdog re-kicks the
|
|
1813
1903
|
* playback chain after timer throttling.
|
|
1904
|
+
*
|
|
1905
|
+
* Frame presentation has two modes picked once at mount by capability
|
|
1906
|
+
* probing:
|
|
1907
|
+
* - Canvas bitmap buffer (default where createImageBitmap/fetch/2D context
|
|
1908
|
+
* exist): every frame is decoded exactly once into an ImageBitmap during
|
|
1909
|
+
* the warm pass and drawn onto one <canvas> - steady-state playback issues
|
|
1910
|
+
* zero DOM mutations and zero re-decodes (measured hotspot: swapping
|
|
1911
|
+
* <img>.src per frame drove image decode + invalidation every tick).
|
|
1912
|
+
* - Classic <img> fallback (jsdom/tests or missing APIs): identical to the
|
|
1913
|
+
* historical behavior - cache-warm Image elements plus guarded src swaps,
|
|
1914
|
+
* so environments without modern decoding keep working unchanged.
|
|
1915
|
+
*
|
|
1814
1916
|
* @module @linxin666/dsh-pet/client/renderers/frames2d
|
|
1815
1917
|
*/
|
|
1816
1918
|
/** Stall watchdog period: a looping track stuck longer re-kicks its chain. */
|
|
@@ -1844,19 +1946,79 @@ window.__ModuleLoader__.load({
|
|
|
1844
1946
|
validateConfig: validateFrames2dConfig,
|
|
1845
1947
|
mount(ctx, config) {
|
|
1846
1948
|
const reducedMotion = typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
img
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1949
|
+
let canvas = null;
|
|
1950
|
+
let context2d = null;
|
|
1951
|
+
let img = null;
|
|
1952
|
+
try {
|
|
1953
|
+
if (typeof createImageBitmap === "function" && typeof fetch === "function") {
|
|
1954
|
+
const probe = document.createElement("canvas");
|
|
1955
|
+
const c2d = probe.getContext("2d");
|
|
1956
|
+
if (c2d !== null) {
|
|
1957
|
+
canvas = probe;
|
|
1958
|
+
context2d = c2d;
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
} catch {
|
|
1962
|
+
canvas = null;
|
|
1963
|
+
context2d = null;
|
|
1859
1964
|
}
|
|
1965
|
+
if (canvas !== null && context2d !== null) {
|
|
1966
|
+
canvas.dataset.dshPetFrames2d = ctx.petId;
|
|
1967
|
+
canvas.draggable = false;
|
|
1968
|
+
canvas.style.width = "100%";
|
|
1969
|
+
canvas.style.height = "100%";
|
|
1970
|
+
canvas.style.objectFit = "contain";
|
|
1971
|
+
canvas.style.pointerEvents = "none";
|
|
1972
|
+
ctx.container.appendChild(canvas);
|
|
1973
|
+
} else {
|
|
1974
|
+
img = document.createElement("img");
|
|
1975
|
+
img.dataset.dshPetFrames2d = ctx.petId;
|
|
1976
|
+
img.alt = "";
|
|
1977
|
+
img.draggable = false;
|
|
1978
|
+
img.style.width = "100%";
|
|
1979
|
+
img.style.height = "100%";
|
|
1980
|
+
img.style.objectFit = "contain";
|
|
1981
|
+
img.style.pointerEvents = "none";
|
|
1982
|
+
ctx.container.appendChild(img);
|
|
1983
|
+
}
|
|
1984
|
+
const decoding = /* @__PURE__ */ new Map();
|
|
1985
|
+
const decodedAll = [];
|
|
1986
|
+
const loadFrame = (url) => {
|
|
1987
|
+
const cached = decoding.get(url);
|
|
1988
|
+
if (cached !== void 0) return cached;
|
|
1989
|
+
const job = (async () => {
|
|
1990
|
+
try {
|
|
1991
|
+
const response = await fetch(url);
|
|
1992
|
+
if (!response.ok) throw new Error("http " + response.status);
|
|
1993
|
+
const bitmap = await createImageBitmap(await response.blob());
|
|
1994
|
+
return {
|
|
1995
|
+
source: bitmap,
|
|
1996
|
+
width: bitmap.width,
|
|
1997
|
+
height: bitmap.height
|
|
1998
|
+
};
|
|
1999
|
+
} catch {
|
|
2000
|
+
return await new Promise((resolve) => {
|
|
2001
|
+
try {
|
|
2002
|
+
const pre = new Image();
|
|
2003
|
+
pre.onload = () => {
|
|
2004
|
+
resolve(pre.naturalWidth > 0 ? {
|
|
2005
|
+
source: pre,
|
|
2006
|
+
width: pre.naturalWidth,
|
|
2007
|
+
height: pre.naturalHeight
|
|
2008
|
+
} : void 0);
|
|
2009
|
+
};
|
|
2010
|
+
pre.onerror = () => resolve(void 0);
|
|
2011
|
+
pre.src = url;
|
|
2012
|
+
} catch {
|
|
2013
|
+
resolve(void 0);
|
|
2014
|
+
}
|
|
2015
|
+
});
|
|
2016
|
+
}
|
|
2017
|
+
})();
|
|
2018
|
+
decoding.set(url, job);
|
|
2019
|
+
decodedAll.push(job.then(() => void 0, () => void 0));
|
|
2020
|
+
return job;
|
|
2021
|
+
};
|
|
1860
2022
|
let disposed = false;
|
|
1861
2023
|
let timer;
|
|
1862
2024
|
let watchdog;
|
|
@@ -1864,13 +2026,35 @@ window.__ModuleLoader__.load({
|
|
|
1864
2026
|
let frameIndex = 0;
|
|
1865
2027
|
let lastAdvance = Date.now();
|
|
1866
2028
|
let override;
|
|
2029
|
+
let drawToken = 0;
|
|
2030
|
+
let lastDrawnUrl;
|
|
1867
2031
|
const trackForPhase = (phase) => {
|
|
1868
2032
|
const mapped = config.phases[phase];
|
|
1869
2033
|
return mapped !== void 0 && config.tracks[mapped] !== void 0 ? mapped : config.phases.idle;
|
|
1870
2034
|
};
|
|
2035
|
+
/** Canvas path: paints the newest requested frame; stale draws drop out. */
|
|
2036
|
+
const paintCanvas = (url) => {
|
|
2037
|
+
if (context2d === null || canvas === null) return;
|
|
2038
|
+
const myToken = ++drawToken;
|
|
2039
|
+
loadFrame(url).then((frame) => {
|
|
2040
|
+
if (disposed || frame === void 0 || myToken !== drawToken) return;
|
|
2041
|
+
if (lastDrawnUrl === url) return;
|
|
2042
|
+
lastDrawnUrl = url;
|
|
2043
|
+
if (canvas.width !== frame.width || canvas.height !== frame.height) {
|
|
2044
|
+
canvas.width = frame.width;
|
|
2045
|
+
canvas.height = frame.height;
|
|
2046
|
+
} else context2d.clearRect(0, 0, canvas.width, canvas.height);
|
|
2047
|
+
context2d.drawImage(frame.source, 0, 0);
|
|
2048
|
+
}).catch(() => {});
|
|
2049
|
+
};
|
|
1871
2050
|
const show = (trackId, index) => {
|
|
1872
2051
|
const url = config.tracks[trackId]?.frames[index];
|
|
1873
|
-
if (url
|
|
2052
|
+
if (url === void 0) return;
|
|
2053
|
+
if (img !== null) {
|
|
2054
|
+
if (img.getAttribute("src") !== url) img.src = url;
|
|
2055
|
+
return;
|
|
2056
|
+
}
|
|
2057
|
+
paintCanvas(url);
|
|
1874
2058
|
};
|
|
1875
2059
|
const schedule = (ms) => {
|
|
1876
2060
|
if (disposed) return;
|
|
@@ -1922,6 +2106,7 @@ window.__ModuleLoader__.load({
|
|
|
1922
2106
|
const expected = (def.durations[frameIndex] ?? 200) + WATCHDOG_MS;
|
|
1923
2107
|
if (Date.now() - lastAdvance > expected) tick();
|
|
1924
2108
|
}, WATCHDOG_MS);
|
|
2109
|
+
for (const warmTrack of Object.values(config.tracks)) for (const warmUrl of warmTrack.frames) loadFrame(warmUrl);
|
|
1925
2110
|
play(track);
|
|
1926
2111
|
let disposedOnce = false;
|
|
1927
2112
|
const dispose = () => {
|
|
@@ -1931,7 +2116,17 @@ window.__ModuleLoader__.load({
|
|
|
1931
2116
|
unsubscribe();
|
|
1932
2117
|
if (timer !== void 0) clearTimeout(timer);
|
|
1933
2118
|
if (watchdog !== void 0) clearInterval(watchdog);
|
|
1934
|
-
|
|
2119
|
+
Promise.allSettled(decodedAll).then(() => {
|
|
2120
|
+
for (const job of decoding.values()) job.then((frame) => {
|
|
2121
|
+
try {
|
|
2122
|
+
const maybeClose = (frame?.source)?.close;
|
|
2123
|
+
if (typeof maybeClose === "function" && frame !== void 0) maybeClose.call(frame.source);
|
|
2124
|
+
} catch {}
|
|
2125
|
+
}).catch(() => {});
|
|
2126
|
+
decoding.clear();
|
|
2127
|
+
});
|
|
2128
|
+
canvas?.remove();
|
|
2129
|
+
img?.remove();
|
|
1935
2130
|
};
|
|
1936
2131
|
ctx.onCleanup(dispose);
|
|
1937
2132
|
return {
|
|
@@ -2584,7 +2779,7 @@ window.__ModuleLoader__.load({
|
|
|
2584
2779
|
}
|
|
2585
2780
|
/** Publish a projection of this form, rebuilt whenever the scope or a draft changes. */
|
|
2586
2781
|
bind(project) {
|
|
2587
|
-
const store =
|
|
2782
|
+
const store = createSnapshotStore(project());
|
|
2588
2783
|
this.listeners.add(() => {
|
|
2589
2784
|
store.set(project());
|
|
2590
2785
|
});
|
|
@@ -2599,7 +2794,7 @@ window.__ModuleLoader__.load({
|
|
|
2599
2794
|
exposed: snapshot.status === "ready",
|
|
2600
2795
|
writable: snapshot.writable,
|
|
2601
2796
|
dirty: plan.length > 0,
|
|
2602
|
-
invalid: plan.some((item) => item.
|
|
2797
|
+
invalid: plan.some((item) => item.judge === void 0),
|
|
2603
2798
|
saving: this.saving,
|
|
2604
2799
|
failed: this.failed,
|
|
2605
2800
|
...this.failedReason === void 0 ? {} : { failedReason: this.failedReason }
|
|
@@ -2649,45 +2844,52 @@ window.__ModuleLoader__.load({
|
|
|
2649
2844
|
};
|
|
2650
2845
|
}
|
|
2651
2846
|
/**
|
|
2652
|
-
* Write every staged edit, then re-seed from
|
|
2847
|
+
* Write every staged edit in one atomic scope mutation, then re-seed from
|
|
2848
|
+
* what the Host accepted.
|
|
2653
2849
|
*
|
|
2654
|
-
*
|
|
2655
|
-
*
|
|
2656
|
-
*
|
|
2657
|
-
*
|
|
2658
|
-
*
|
|
2659
|
-
*
|
|
2660
|
-
*
|
|
2850
|
+
* The whole batch rides one mutate, so cross-field validate hooks
|
|
2851
|
+
* (baseURL+model) judge it as a unit: the Host either applies every write
|
|
2852
|
+
* or refuses the batch. The 0.1.2 scope contract never rejects a refused
|
|
2853
|
+
* mutation — the scope recovers with a fresh Host view and resolves — so
|
|
2854
|
+
* resolution alone proves nothing: the outcome is judged by reading the
|
|
2855
|
+
* settled snapshot back, one planned write at a time, and one missed write
|
|
2856
|
+
* fails the whole save. A scope that still rejects on refusal (the dsh-web
|
|
2857
|
+
* bridge scope) reports through the same failure path with its rejection
|
|
2858
|
+
* message. A save that did not land keeps its drafts, so the user can
|
|
2859
|
+
* correct them instead of retyping.
|
|
2860
|
+
* @returns settlement after the mutation and the read-back.
|
|
2661
2861
|
*/
|
|
2662
2862
|
async save() {
|
|
2663
2863
|
const plan = this.plan();
|
|
2664
|
-
const valid = plan.filter((item) => item.
|
|
2864
|
+
const valid = plan.filter((item) => item.judge !== void 0);
|
|
2665
2865
|
if (plan.length === 0 || this.saving || valid.length !== plan.length) return;
|
|
2666
|
-
const plannedWrites = valid.map((item) => item.op);
|
|
2667
2866
|
const pending = /* @__PURE__ */ new Map();
|
|
2668
2867
|
for (const item of plan) pending.set(item.field, this.staged.get(item.field));
|
|
2669
2868
|
this.saving = true;
|
|
2670
2869
|
this.failed = false;
|
|
2671
2870
|
this.failedReason = void 0;
|
|
2672
2871
|
this.publish();
|
|
2673
|
-
const
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
}
|
|
2681
|
-
|
|
2872
|
+
const ops = valid.map((item) => item.op.op === "set" ? {
|
|
2873
|
+
op: "set",
|
|
2874
|
+
path: [item.field],
|
|
2875
|
+
value: item.op.value
|
|
2876
|
+
} : {
|
|
2877
|
+
op: "unset",
|
|
2878
|
+
path: [item.field]
|
|
2879
|
+
});
|
|
2880
|
+
let failedReason;
|
|
2881
|
+
try {
|
|
2882
|
+
await this.scope.mutate(ops);
|
|
2883
|
+
} catch (error) {
|
|
2884
|
+
failedReason = error instanceof Error ? error.message : String(error);
|
|
2885
|
+
}
|
|
2886
|
+
const landed = failedReason === void 0 && valid.every((item) => item.judge());
|
|
2887
|
+
for (const [field, before] of pending) if (landed && this.staged.get(field) === before) this.staged.delete(field);
|
|
2682
2888
|
this.saving = false;
|
|
2683
|
-
this.failed = landed
|
|
2889
|
+
this.failed = !landed;
|
|
2890
|
+
this.failedReason = failedReason;
|
|
2684
2891
|
this.publish();
|
|
2685
2892
|
}
|
|
2686
|
-
/** The scope's batch surface when it supports one; undefined conservatively otherwise. */
|
|
2687
|
-
batchedScope() {
|
|
2688
|
-
const candidate = this.scope;
|
|
2689
|
-
return typeof candidate?.mutate === "function" ? candidate : void 0;
|
|
2690
|
-
}
|
|
2691
2893
|
/**
|
|
2692
2894
|
* Every staged edit a save would write. An entry whose draft is not a value
|
|
2693
2895
|
* its field accepts carries no write: the form is still dirty, and the save
|
|
@@ -2706,7 +2908,7 @@ window.__ModuleLoader__.load({
|
|
|
2706
2908
|
field,
|
|
2707
2909
|
op: "unset"
|
|
2708
2910
|
},
|
|
2709
|
-
|
|
2911
|
+
judge: () => this.landedUnset(field)
|
|
2710
2912
|
});
|
|
2711
2913
|
continue;
|
|
2712
2914
|
}
|
|
@@ -2718,7 +2920,7 @@ window.__ModuleLoader__.load({
|
|
|
2718
2920
|
field,
|
|
2719
2921
|
op: "unset"
|
|
2720
2922
|
},
|
|
2721
|
-
|
|
2923
|
+
judge: void 0
|
|
2722
2924
|
});
|
|
2723
2925
|
else if (write.kind === "clear") plan.push({
|
|
2724
2926
|
field,
|
|
@@ -2726,7 +2928,7 @@ window.__ModuleLoader__.load({
|
|
|
2726
2928
|
field,
|
|
2727
2929
|
op: "unset"
|
|
2728
2930
|
},
|
|
2729
|
-
|
|
2931
|
+
judge: () => this.landedUnset(field)
|
|
2730
2932
|
});
|
|
2731
2933
|
else plan.push({
|
|
2732
2934
|
field,
|
|
@@ -2735,20 +2937,26 @@ window.__ModuleLoader__.load({
|
|
|
2735
2937
|
op: "set",
|
|
2736
2938
|
value: write.value
|
|
2737
2939
|
},
|
|
2738
|
-
|
|
2940
|
+
judge: () => this.landedSet(field, write.value)
|
|
2739
2941
|
});
|
|
2740
2942
|
}
|
|
2741
2943
|
return plan;
|
|
2742
2944
|
}
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
await this.scope.set(field, value);
|
|
2945
|
+
/**
|
|
2946
|
+
* Read-back judgment for a planned set: the user layer must hold the
|
|
2947
|
+
* intended value once the mutation has settled.
|
|
2948
|
+
*/
|
|
2949
|
+
landedSet(field, value) {
|
|
2749
2950
|
if (this.specOf(field).secret) return true;
|
|
2750
2951
|
return this.userLayer()?.[field] === value;
|
|
2751
2952
|
}
|
|
2953
|
+
/**
|
|
2954
|
+
* Read-back judgment for a planned unset: the field must be gone from the
|
|
2955
|
+
* user layer once the mutation has settled.
|
|
2956
|
+
*/
|
|
2957
|
+
landedUnset(field) {
|
|
2958
|
+
return !this.stored(field);
|
|
2959
|
+
}
|
|
2752
2960
|
stage(field, edit) {
|
|
2753
2961
|
this.staged.set(field, edit);
|
|
2754
2962
|
this.failed = false;
|
|
@@ -3081,6 +3289,15 @@ window.__ModuleLoader__.load({
|
|
|
3081
3289
|
"pet.namePlaceholder": "输入新名字",
|
|
3082
3290
|
"pet.summon": "召唤{name}",
|
|
3083
3291
|
"pet.rank": "亲密度 {rank}",
|
|
3292
|
+
"pet.rank.name.幼鲸": "幼鲸",
|
|
3293
|
+
"pet.rank.name.伙伴": "伙伴",
|
|
3294
|
+
"pet.rank.name.挚友": "挚友",
|
|
3295
|
+
"pet.rank.name.深海羁绊": "深海羁绊",
|
|
3296
|
+
"pet.rank.name.心有灵犀": "心有灵犀",
|
|
3297
|
+
"pet.rank.name.传说羁绊": "传说羁绊",
|
|
3298
|
+
"pet.rank.name.神话羁绊": "神话羁绊",
|
|
3299
|
+
"pet.rank.name.永恒之契": "永恒之契",
|
|
3300
|
+
"pet.rank.name.鲸生共渡": "鲸生共渡",
|
|
3084
3301
|
"pet.points": "{points} 点",
|
|
3085
3302
|
"pet.treats": "小鱼干 ×{n}",
|
|
3086
3303
|
"pet.state.loading": "宠物正在赶来…",
|
|
@@ -3131,7 +3348,7 @@ window.__ModuleLoader__.load({
|
|
|
3131
3348
|
"settings.off": "关",
|
|
3132
3349
|
"settings.overridden": "已覆盖",
|
|
3133
3350
|
"settings.reset": "恢复默认",
|
|
3134
|
-
"settings.notExposed": "当前 DSH 版本未向设置页暴露本插件的配置命名空间,表单不可用。可编辑
|
|
3351
|
+
"settings.notExposed": "当前 DSH 版本未向设置页暴露本插件的配置命名空间,表单不可用。可编辑 $DSH_HOME/settings.yaml 直接配置,或确认提供该命名空间的插件已挂载其设置域并重启。",
|
|
3135
3352
|
"settings.readOnly": "当前部署的设置只读。",
|
|
3136
3353
|
"settings.expand": "展开设置",
|
|
3137
3354
|
"settings.collapse": "收起设置",
|
|
@@ -3151,6 +3368,15 @@ window.__ModuleLoader__.load({
|
|
|
3151
3368
|
"pet.namePlaceholder": "Enter a new name",
|
|
3152
3369
|
"pet.summon": "Summon {name}",
|
|
3153
3370
|
"pet.rank": "Affinity {rank}",
|
|
3371
|
+
"pet.rank.name.幼鲸": "Baby Whale",
|
|
3372
|
+
"pet.rank.name.伙伴": "Companion",
|
|
3373
|
+
"pet.rank.name.挚友": "Close Friend",
|
|
3374
|
+
"pet.rank.name.深海羁绊": "Deep Sea Bond",
|
|
3375
|
+
"pet.rank.name.心有灵犀": "Kindred Spirit",
|
|
3376
|
+
"pet.rank.name.传说羁绊": "Legendary Bond",
|
|
3377
|
+
"pet.rank.name.神话羁绊": "Mythic Bond",
|
|
3378
|
+
"pet.rank.name.永恒之契": "Eternal Covenant",
|
|
3379
|
+
"pet.rank.name.鲸生共渡": "Lifelong Companion",
|
|
3154
3380
|
"pet.points": "{points} pts",
|
|
3155
3381
|
"pet.treats": "Treats ×{n}",
|
|
3156
3382
|
"pet.state.loading": "The pet is on its way…",
|
|
@@ -3201,7 +3427,7 @@ window.__ModuleLoader__.load({
|
|
|
3201
3427
|
"settings.off": "Off",
|
|
3202
3428
|
"settings.overridden": "Overridden",
|
|
3203
3429
|
"settings.reset": "Reset to default",
|
|
3204
|
-
"settings.notExposed": "This DSH version does not expose this plugin's settings namespace to the configuration page, so the form is unavailable. Edit
|
|
3430
|
+
"settings.notExposed": "This DSH version does not expose this plugin's settings namespace to the configuration page, so the form is unavailable. Edit $DSH_HOME/settings.yaml directly, or confirm that the plugin owning the namespace is mounted with its settings domain and restart.",
|
|
3205
3431
|
"settings.readOnly": "This deployment stores settings read-only.",
|
|
3206
3432
|
"settings.expand": "Show settings",
|
|
3207
3433
|
"settings.collapse": "Hide settings",
|
|
@@ -3242,7 +3468,7 @@ window.__ModuleLoader__.load({
|
|
|
3242
3468
|
/** The building package's version, when the bundle carries it. */
|
|
3243
3469
|
function bakedVersion() {
|
|
3244
3470
|
try {
|
|
3245
|
-
return "0.3.
|
|
3471
|
+
return "0.3.9";
|
|
3246
3472
|
} catch {
|
|
3247
3473
|
return;
|
|
3248
3474
|
}
|
|
@@ -3524,7 +3750,8 @@ window.__ModuleLoader__.load({
|
|
|
3524
3750
|
const petRoot = (0, react_dom_client.createRoot)(container);
|
|
3525
3751
|
petRoot.render((0, react.createElement)(PetDockEntry, {
|
|
3526
3752
|
...injected(),
|
|
3527
|
-
t
|
|
3753
|
+
t,
|
|
3754
|
+
portalTarget: container
|
|
3528
3755
|
}));
|
|
3529
3756
|
let uiGone = false;
|
|
3530
3757
|
disposeUi = () => {
|