modelstage 0.0.0-stage → 0.5.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (286) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +267 -2
  3. package/THIRD_PARTY_NOTICES.md +90 -0
  4. package/bin/modelstage.mjs +101 -0
  5. package/config/diag-upload.example.json +7 -0
  6. package/config/models/live2d.assetbundls.json +1 -0
  7. package/config/models/live2d.azurlane.json +1 -0
  8. package/config/models/live2d.azurlane_bundle.json +1 -0
  9. package/config/models/live2d.azurlane_spine.json +1 -0
  10. package/config/models/live2d.bd2.json +1 -0
  11. package/config/models/live2d.bluearchive.json +1 -0
  12. package/config/models/live2d.live2d.json +1 -0
  13. package/config/models/live2d.nikke.json +1 -0
  14. package/config/models/live2d.nikke_2.json +1 -0
  15. package/config/models/live2d.npc.json +1 -0
  16. package/config/models/spine.assetbundls.json +1 -0
  17. package/config/models/spine.azurlane.json +1 -0
  18. package/config/models/spine.azurlane_bundle.json +1 -0
  19. package/config/models/spine.azurlane_spine.json +1 -0
  20. package/config/models/spine.bd2.json +1 -0
  21. package/config/models/spine.bluearchive.json +1 -0
  22. package/config/models/spine.live2d.json +1 -0
  23. package/config/models/spine.nikke.json +1 -0
  24. package/config/models/spine.nikke_2.json +1 -0
  25. package/config/models/spine.npc.json +1 -0
  26. package/config/names/NIKKE.json +1058 -0
  27. package/config/names/README.md +47 -0
  28. package/config/names//345/244/251/345/221/275/344/271/213/345/255/220.json +42897 -0
  29. package/config/names//346/243/225/350/211/262/345/260/230/345/237/2032.json +4016 -0
  30. package/config/names//347/242/247/350/223/235/350/210/252/347/272/277.json +38342 -0
  31. package/config/plugins.json +13 -0
  32. package/config/resources.example.json +40 -0
  33. package/package.json +58 -4
  34. package/plugins/unity-live2d-bundle/README.md +98 -0
  35. package/plugins/unity-live2d-bundle/index.js +891 -0
  36. package/plugins/unity-live2d-bundle/lib/anim-to-motion.js +402 -0
  37. package/plugins/unity-live2d-bundle/lib/bundle-probe.js +244 -0
  38. package/plugins/unity-live2d-bundle/lib/bytes.js +189 -0
  39. package/plugins/unity-live2d-bundle/lib/common-strings.js +46 -0
  40. package/plugins/unity-live2d-bundle/lib/dxt.js +197 -0
  41. package/plugins/unity-live2d-bundle/lib/errors.js +71 -0
  42. package/plugins/unity-live2d-bundle/lib/etc2.js +371 -0
  43. package/plugins/unity-live2d-bundle/lib/extract.js +242 -0
  44. package/plugins/unity-live2d-bundle/lib/lzma-d-min.LICENSE.txt +21 -0
  45. package/plugins/unity-live2d-bundle/lib/lzma-d-min.js +34 -0
  46. package/plugins/unity-live2d-bundle/lib/model3.js +137 -0
  47. package/plugins/unity-live2d-bundle/lib/png.js +424 -0
  48. package/plugins/unity-live2d-bundle/lib/serialized.js +233 -0
  49. package/plugins/unity-live2d-bundle/lib/texdecode.js +190 -0
  50. package/plugins/unity-live2d-bundle/lib/typetree.js +215 -0
  51. package/plugins/unity-live2d-bundle/lib/unityfs.js +396 -0
  52. package/plugins/unity-live2d-bundle/plugin.json +81 -0
  53. package/plugins/unity-live2d-bundle/profiles/unityfs-live2d.json +26 -0
  54. package/plugins/unity-spine-bundle/index.js +535 -0
  55. package/plugins/unity-spine-bundle/lib/bytes.js +189 -0
  56. package/plugins/unity-spine-bundle/lib/common-strings.js +46 -0
  57. package/plugins/unity-spine-bundle/lib/dxt.js +197 -0
  58. package/plugins/unity-spine-bundle/lib/errors.js +79 -0
  59. package/plugins/unity-spine-bundle/lib/etc2.js +371 -0
  60. package/plugins/unity-spine-bundle/lib/extract.js +242 -0
  61. package/plugins/unity-spine-bundle/lib/lzma-d-min.LICENSE.txt +21 -0
  62. package/plugins/unity-spine-bundle/lib/lzma-d-min.js +34 -0
  63. package/plugins/unity-spine-bundle/lib/png.js +424 -0
  64. package/plugins/unity-spine-bundle/lib/serialized.js +233 -0
  65. package/plugins/unity-spine-bundle/lib/texdecode.js +190 -0
  66. package/plugins/unity-spine-bundle/lib/typetree.js +215 -0
  67. package/plugins/unity-spine-bundle/lib/unityfs.js +396 -0
  68. package/plugins/unity-spine-bundle/plugin.json +75 -0
  69. package/src/server/README.md +69 -0
  70. package/src/server/assets/browse.mjs +56 -0
  71. package/src/server/assets/bundle-probe.mjs +94 -0
  72. package/src/server/assets/probe.mjs +184 -0
  73. package/src/server/core/config.mjs +173 -0
  74. package/src/server/core/env-num.mjs +60 -0
  75. package/src/server/core/env.mjs +49 -0
  76. package/src/server/core/fs-portable.mjs +37 -0
  77. package/src/server/core/log.mjs +107 -0
  78. package/src/server/core/paths.mjs +44 -0
  79. package/src/server/data/catalog.mjs +1091 -0
  80. package/src/server/data/entry-states.mjs +499 -0
  81. package/src/server/data/live2d.mjs +272 -0
  82. package/src/server/data/manifest-cache.mjs +58 -0
  83. package/src/server/data/manifest-stamp.mjs +135 -0
  84. package/src/server/data/model-meta.mjs +114 -0
  85. package/src/server/data/names.mjs +106 -0
  86. package/src/server/data/shape-probe-worker.mjs +33 -0
  87. package/src/server/data/shape-probe.mjs +626 -0
  88. package/src/server/data/spine.mjs +336 -0
  89. package/src/server/data/tree.mjs +126 -0
  90. package/src/server/http/atomic-json.mjs +145 -0
  91. package/src/server/http/background-solids.mjs +270 -0
  92. package/src/server/http/backgrounds.mjs +388 -0
  93. package/src/server/http/diag-upload.mjs +264 -0
  94. package/src/server/http/diag.mjs +173 -0
  95. package/src/server/http/ready.mjs +167 -0
  96. package/src/server/http/static.mjs +312 -0
  97. package/src/server/http/write.mjs +647 -0
  98. package/src/server/index.mjs +114 -0
  99. package/src/server/plugins/builtin-providers.json +30 -0
  100. package/src/server/plugins/registry.mjs +650 -0
  101. package/src/server/plugins/slot-catalog.mjs +286 -0
  102. package/src/server/routes/background-solids.mjs +56 -0
  103. package/src/server/routes/backgrounds.mjs +77 -0
  104. package/src/server/routes/config.mjs +38 -0
  105. package/src/server/routes/data.mjs +313 -0
  106. package/src/server/routes/diag.mjs +37 -0
  107. package/src/server/routes/entries.mjs +170 -0
  108. package/src/server/routes/ready.mjs +122 -0
  109. package/src/server/serve.mjs +334 -0
  110. package/src/server/unpack/capacity.mjs +217 -0
  111. package/src/server/unpack/mem-probe.mjs +27 -0
  112. package/src/server/unpack/plugin-runner.mjs +529 -0
  113. package/src/server/unpack/plugin-store.mjs +736 -0
  114. package/src/server/unpack/probe-store.mjs +73 -0
  115. package/src/server/unpack/routes.mjs +359 -0
  116. package/src/server/unpack/runtime/exec.mjs +558 -0
  117. package/src/server/unpack/runtime/index.mjs +141 -0
  118. package/src/server/unpack/runtime/node-vm.mjs +111 -0
  119. package/src/server/unpack/unpack-worker.mjs +105 -0
  120. package/src/server/unpack/worker-client.mjs +184 -0
  121. package/src/server/unpack/write-result.mjs +84 -0
  122. package/src/shared/bundle-shape.mjs +83 -0
  123. package/src/shared/entry-reasons.mjs +119 -0
  124. package/src/shared/names.mjs +45 -0
  125. package/src/web/README.md +193 -0
  126. package/src/web/app/anim-list.js +222 -0
  127. package/src/web/app/background-solids.js +150 -0
  128. package/src/web/app/backgrounds.js +152 -0
  129. package/src/web/app/camera-log.js +28 -0
  130. package/src/web/app/context.js +72 -0
  131. package/src/web/app/dir-picker.js +242 -0
  132. package/src/web/app/entry-report.js +71 -0
  133. package/src/web/app/memory-bars.js +91 -0
  134. package/src/web/app/memory-budget.js +228 -0
  135. package/src/web/app/memory-guard.js +204 -0
  136. package/src/web/app/model-actions.js +682 -0
  137. package/src/web/app/renderer-setting.js +70 -0
  138. package/src/web/app/root-manager.js +254 -0
  139. package/src/web/app/status.js +138 -0
  140. package/src/web/app/topbar-actions.js +357 -0
  141. package/src/web/app/transport-actions.js +297 -0
  142. package/src/web/app/ui-state.js +559 -0
  143. package/src/web/app/url-params.js +171 -0
  144. package/src/web/app/view-settings.js +186 -0
  145. package/src/web/components.css +465 -0
  146. package/src/web/debug/diag-hud.js +95 -0
  147. package/src/web/debug/drag.js +369 -0
  148. package/src/web/debug/log.js +320 -0
  149. package/src/web/debug/model-cmp.js +292 -0
  150. package/src/web/debug/panel.css +474 -0
  151. package/src/web/debug/panel.js +712 -0
  152. package/src/web/debug/race-probe.js +82 -0
  153. package/src/web/debug/upload.js +130 -0
  154. package/src/web/engine/adapters/atlas.js +47 -0
  155. package/src/web/engine/adapters/index.js +22 -0
  156. package/src/web/engine/adapters/renderer.js +223 -0
  157. package/src/web/engine/adapters/skeleton.js +105 -0
  158. package/src/web/engine/adapters/texture.js +64 -0
  159. package/src/web/engine/animation.js +181 -0
  160. package/src/web/engine/appearance.js +136 -0
  161. package/src/web/engine/camera-adapter.js +212 -0
  162. package/src/web/engine/effect-chain.js +138 -0
  163. package/src/web/engine/engine-state.js +120 -0
  164. package/src/web/engine/filters.js +224 -0
  165. package/src/web/engine/fit.js +197 -0
  166. package/src/web/engine/frame-gate.js +33 -0
  167. package/src/web/engine/gl-driver.js +56 -0
  168. package/src/web/engine/gl.js +77 -0
  169. package/src/web/engine/index.js +20 -0
  170. package/src/web/engine/loader.js +55 -0
  171. package/src/web/engine/model-info.js +26 -0
  172. package/src/web/engine/pipeline.js +200 -0
  173. package/src/web/engine/player.js +427 -0
  174. package/src/web/engine/pma/blend.js +76 -0
  175. package/src/web/engine/pma/detect.js +118 -0
  176. package/src/web/engine/pma/index.js +8 -0
  177. package/src/web/engine/pma/resolve.js +112 -0
  178. package/src/web/engine/pma/upload.js +127 -0
  179. package/src/web/engine/pma.js +5 -0
  180. package/src/web/engine/render-frame.js +93 -0
  181. package/src/web/engine/runtime-capabilities.js +209 -0
  182. package/src/web/engine/runtime.js +37 -0
  183. package/src/web/engine/skins.js +165 -0
  184. package/src/web/engine/textures.js +174 -0
  185. package/src/web/index.html +115 -0
  186. package/src/web/infra/api.js +531 -0
  187. package/src/web/infra/diag.js +509 -0
  188. package/src/web/infra/document.js +269 -0
  189. package/src/web/infra/dom.js +31 -0
  190. package/src/web/infra/events.mjs +27 -0
  191. package/src/web/infra/log.js +153 -0
  192. package/src/web/infra/mem-stats.js +112 -0
  193. package/src/web/infra/plugin-host-registration.mjs +285 -0
  194. package/src/web/infra/plugin-host-report.mjs +32 -0
  195. package/src/web/infra/plugin-host-unload.mjs +79 -0
  196. package/src/web/infra/plugin-host-unpack.mjs +899 -0
  197. package/src/web/infra/plugin-host-warmup.mjs +180 -0
  198. package/src/web/infra/plugin-host.js +628 -0
  199. package/src/web/infra/ready.js +461 -0
  200. package/src/web/infra/schedule.js +51 -0
  201. package/src/web/infra/shortcuts.js +129 -0
  202. package/src/web/infra/slot-registry.js +402 -0
  203. package/src/web/infra/vfs.js +247 -0
  204. package/src/web/live2d/bridge.js +400 -0
  205. package/src/web/live2d/model.js +321 -0
  206. package/src/web/live2d/overlay.js +142 -0
  207. package/src/web/live2d/pixi-app.js +111 -0
  208. package/src/web/live2d/playback-probe.js +92 -0
  209. package/src/web/live2d/probe-geometry.js +329 -0
  210. package/src/web/live2d/probe.js +119 -0
  211. package/src/web/live2d/release.js +66 -0
  212. package/src/web/live2d/runtime-adapter.js +289 -0
  213. package/src/web/live2d/stage-ctx.js +152 -0
  214. package/src/web/live2d/stage.js +204 -0
  215. package/src/web/live2d/vfs-settings.js +174 -0
  216. package/src/web/live2d/view.js +89 -0
  217. package/src/web/main.js +629 -0
  218. package/src/web/panel/background-list.js +373 -0
  219. package/src/web/panel/bg-source.js +291 -0
  220. package/src/web/panel/color-picker.css +199 -0
  221. package/src/web/panel/color-picker.js +278 -0
  222. package/src/web/panel/dialog.css +116 -0
  223. package/src/web/panel/dialog.js +173 -0
  224. package/src/web/panel/dom.js +35 -0
  225. package/src/web/panel/icons.js +58 -0
  226. package/src/web/panel/memory-bar.js +195 -0
  227. package/src/web/panel/panel-settings.js +1263 -0
  228. package/src/web/panel/panel.css +1304 -0
  229. package/src/web/panel/render-choice.js +104 -0
  230. package/src/web/panel/schema.js +784 -0
  231. package/src/web/panel/settings-io.js +291 -0
  232. package/src/web/panel/shell-controls.js +274 -0
  233. package/src/web/panel/slider.js +210 -0
  234. package/src/web/panel/toolbar-schema.js +111 -0
  235. package/src/web/panel/toolbar.js +195 -0
  236. package/src/web/panel/transport-picker.js +375 -0
  237. package/src/web/panel/transport.js +321 -0
  238. package/src/web/panel/view.js +553 -0
  239. package/src/web/plugin/unpack-worker.js +185 -0
  240. package/src/web/render/camera/constants.js +60 -0
  241. package/src/web/render/camera/index.js +123 -0
  242. package/src/web/render/camera/spine.js +124 -0
  243. package/src/web/render/capture.js +30 -0
  244. package/src/web/render/interaction.js +527 -0
  245. package/src/web/render/live2d.js +627 -0
  246. package/src/web/render/playback.js +47 -0
  247. package/src/web/render/reset-view.js +29 -0
  248. package/src/web/render/spine.js +386 -0
  249. package/src/web/render/surface-switch.js +176 -0
  250. package/src/web/render/surface.js +571 -0
  251. package/src/web/render/surfaces.js +70 -0
  252. package/src/web/roster/filter.js +31 -0
  253. package/src/web/roster/list.js +276 -0
  254. package/src/web/roster/persist.js +52 -0
  255. package/src/web/roster/reveal.js +77 -0
  256. package/src/web/roster/rows.js +248 -0
  257. package/src/web/roster/snapshot.js +37 -0
  258. package/src/web/roster/view.js +670 -0
  259. package/src/web/spine/canvas-api-groups.js +267 -0
  260. package/src/web/spine/canvas-api.js +97 -0
  261. package/src/web/spine/canvas-document.js +80 -0
  262. package/src/web/spine/canvas-engine.js +193 -0
  263. package/src/web/spine/canvas-entry.js +28 -0
  264. package/src/web/spine/canvas-pixels.js +36 -0
  265. package/src/web/spine/canvas-runtime.js +127 -0
  266. package/src/web/spine/canvas-view.js +108 -0
  267. package/src/web/spine/canvas.js +7 -0
  268. package/src/web/spine/runtime-hint.js +73 -0
  269. package/src/web/spine/runtimes.js +212 -0
  270. package/src/web/style.css +493 -0
  271. package/tool/lib/bundle-probe-node.mjs +121 -0
  272. package/tool/lib/check-perms.mjs +114 -0
  273. package/tool/lib/contract-checks.mjs +582 -0
  274. package/tool/lib/cubism-core.mjs +122 -0
  275. package/tool/lib/export-bundle.mjs +328 -0
  276. package/tool/lib/paths.mjs +20 -0
  277. package/tool/lib/runtime-guides.mjs +65 -0
  278. package/tool/lib/runtime-package.mjs +403 -0
  279. package/tool/lib/spine-meta.mjs +227 -0
  280. package/tool/lib/spine-motions.mjs +104 -0
  281. package/tool/lib/spine-runtime.mjs +131 -0
  282. package/tool/lib/test-lock.mjs +178 -0
  283. package/tool/lib/test-report.mjs +133 -0
  284. package/tool/parse-jsonc.mjs +86 -0
  285. package/tool/resolve-live2d-runtimes.mjs +276 -0
  286. package/tool/spine-motion-rules.json +101 -0
@@ -0,0 +1,493 @@
1
+ /* ============================================================================
2
+ ModelStage 本实现 · 布局与基础样式(M1)
3
+ - 色板为本项目自有令牌(--color-background / --color-card …,五套主题各一份,见 docs/设计/界面与状态.md),
4
+ 目的是"看起来一样",不是重新设计。
5
+ - 五个区域的几何值按 1920×1080 实测标定,见 index.html 头注释。
6
+ - 这个文件只管:变量、基础重置、舞台、五个区域的容器几何。
7
+ 控件外观(按钮、列表、横幅)在 components.css。
8
+ ========================================================================== */
9
+
10
+ /* [hidden] 兜底 —— 必须写在所有容器样式之前,且用 !important。
11
+ 原因:`el.hidden = true` 只是加 HTML 属性,UA 的 `[hidden] { display: none }` 会被
12
+ 作者样式里的 `display:` 直接覆盖(两者同为元素选择器权重,作者样式优先),
13
+ 结果是"属性设了、界面照样显示"的静默失效。
14
+ 历史:.stage-hint[hidden] 与 .acc-body[hidden] 都是逐类补过的 —— 加了这条后它们成为冗余
15
+ (保留无害),但新容器不要再逐类打补丁。
16
+ 为什么用 !important:[hidden] 的语义就是"不显示",任何作者样式都不该把它变回来。 */
17
+ [hidden] {
18
+ display: none !important;
19
+ }
20
+
21
+ :root {
22
+ /* 主色板(五套主题共用同一批令牌名) */
23
+ --color-background: #131416;
24
+ --color-foreground: #e9ebed;
25
+ --color-card: #1f2326;
26
+ --color-primary: #1fc0c5;
27
+ --color-secondary: #2a2f33;
28
+ --color-muted: #23272a;
29
+ --color-muted-foreground: #9ca3af;
30
+ /* 诊断/说明类小字专用文字色(面板四行诊断、只读提示等)。语义独立于 muted-foreground:将来只想调诊断字深浅时改这一处即可。各主题块里都有同名定义。 */
31
+ --color-text-secondary: #9ca3af;
32
+ --color-border: #353a3e;
33
+ --scroll-thumb: #2e2e38;
34
+ --scroll-thumb-hover: #3e3e48;
35
+ --color-danger: #e06c75;
36
+
37
+ /* 布局尺寸 */
38
+ --header-h: 3rem; /* 48px */
39
+ --left-w: 19rem; /* 304px */
40
+ --right-w: 15rem; /* 240px(176px 太窄:控件名被压成一个字、按钮竖排) */
41
+ --footer-h: 2.625rem; /* 42px */
42
+ --panel-bottom: 4rem; /* 侧栏底边留 64px(旧 aside bottom:64px) */
43
+ --radius: 8px;
44
+ }
45
+
46
+ /* ---------------------------------------------------------------- 基础 */
47
+
48
+ *,
49
+ *::before,
50
+ *::after {
51
+ box-sizing: border-box;
52
+ }
53
+
54
+ html,
55
+ body {
56
+ height: 100%;
57
+ margin: 0;
58
+ padding: 0;
59
+ /* 锁滚动:舞台铺满视口,页面永不出现滚动条(含横向) */
60
+ overflow: hidden;
61
+ background: var(--color-background);
62
+ color: var(--color-foreground);
63
+ font-family: system-ui, -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
64
+ -webkit-tap-highlight-color: transparent;
65
+ }
66
+
67
+ /* 字号只设在 body 上:html 保持浏览器默认 16px,
68
+ 否则下面的 rem 尺寸(--header-h 等)会按 13px 折算,整页小一圈 */
69
+ body {
70
+ font-size: 13px;
71
+ }
72
+
73
+ button {
74
+ font: inherit;
75
+ color: inherit;
76
+ border: 0;
77
+ background: transparent;
78
+ cursor: pointer;
79
+ }
80
+
81
+ button:disabled {
82
+ cursor: default;
83
+ opacity: 0.45;
84
+ }
85
+
86
+ /* 滚动条:细窄、暗色 */
87
+ ::-webkit-scrollbar {
88
+ width: 5px;
89
+ height: 5px;
90
+ }
91
+ ::-webkit-scrollbar-track {
92
+ background: transparent;
93
+ }
94
+ ::-webkit-scrollbar-thumb {
95
+ background: var(--scroll-thumb);
96
+ border-radius: 3px;
97
+ }
98
+ ::-webkit-scrollbar-thumb:hover {
99
+ background: var(--scroll-thumb-hover);
100
+ }
101
+
102
+ /* ------------------------------------------------------------ 舞台(铺满) */
103
+
104
+ .stage {
105
+ position: fixed;
106
+ inset: 0;
107
+ z-index: 0;
108
+ }
109
+
110
+ /* 棋盘格:格边长 14px,大屏 24px */
111
+ .checkerboard {
112
+ --cb: 14px;
113
+ background-color: #17191b;
114
+ background-image:
115
+ linear-gradient(45deg, #1c1f21 25%, transparent 25%),
116
+ linear-gradient(-45deg, #1c1f21 25%, transparent 25%),
117
+ linear-gradient(45deg, transparent 75%, #1c1f21 75%),
118
+ linear-gradient(-45deg, transparent 75%, #1c1f21 75%);
119
+ background-size: var(--cb) var(--cb);
120
+ background-position: 0 0, 0 calc(var(--cb) / 2), calc(var(--cb) / 2) calc(var(--cb) / -2), calc(var(--cb) / -2) 0;
121
+ }
122
+
123
+ @media (min-width: 1024px) {
124
+ .checkerboard {
125
+ --cb: 24px;
126
+ }
127
+ }
128
+
129
+ .stage-canvas {
130
+ position: absolute;
131
+ inset: 0;
132
+ width: 100%;
133
+ height: 100%;
134
+ display: block;
135
+ }
136
+
137
+ /* 舞台状态提示:放左下角小字,不遮挡画面主体。
138
+ 正常的状态文字一律走日志;这里只用于「失败 / 不可用 / 未接入」这类需要用户看见的情况。 */
139
+ .stage-hint {
140
+ position: absolute;
141
+ left: 12px;
142
+ bottom: 12px;
143
+ transform: none;
144
+ margin: 0;
145
+ padding: 4px 10px;
146
+ max-width: calc(100% - 24px);
147
+ border: 1px solid var(--color-border);
148
+ border-radius: var(--radius);
149
+ background: rgba(31, 35, 38, 0.72);
150
+ color: var(--color-muted-foreground);
151
+ font-size: 11px;
152
+ line-height: 1.4;
153
+ overflow: hidden;
154
+ text-overflow: ellipsis;
155
+ white-space: nowrap;
156
+ pointer-events: none;
157
+ }
158
+
159
+ .stage-hint[hidden] {
160
+ display: none;
161
+ }
162
+
163
+ /* ------------------------------------------------------ 顶栏(top 0,全宽) */
164
+
165
+ .app-header {
166
+ position: fixed;
167
+ top: 0;
168
+ left: 0;
169
+ right: 0;
170
+ height: var(--header-h);
171
+ display: flex;
172
+ align-items: center;
173
+ justify-content: space-between;
174
+ gap: 8px;
175
+ padding: 0 16px;
176
+ background: var(--color-card);
177
+ color: var(--color-foreground);
178
+ z-index: 40;
179
+ }
180
+
181
+ /* ------------------------------------------------- 侧栏(左/右,浮在舞台上) */
182
+
183
+ .panel {
184
+ position: fixed;
185
+ top: var(--header-h);
186
+ bottom: var(--panel-bottom);
187
+ display: flex;
188
+ flex-direction: column;
189
+ background: var(--color-card);
190
+ color: var(--color-foreground);
191
+ z-index: 30;
192
+ overflow: hidden;
193
+ /* 面板显示/隐藏用位移动画,不是 display 切换 */
194
+ transition: transform 0.2s ease, opacity 0.2s ease;
195
+ }
196
+
197
+ .panel-left {
198
+ left: 0;
199
+ width: var(--left-w);
200
+ border-radius: 0 var(--radius) var(--radius) 0;
201
+ }
202
+
203
+ .panel-right {
204
+ right: 0;
205
+ width: var(--right-w);
206
+ border-radius: var(--radius) 0 0 var(--radius);
207
+ }
208
+
209
+ .panel-left.is-hidden {
210
+ transform: translateX(-100%);
211
+ opacity: 0;
212
+ pointer-events: none;
213
+ }
214
+
215
+ .panel-head {
216
+ flex: none;
217
+ display: flex;
218
+ align-items: center;
219
+ justify-content: space-between;
220
+ gap: 6px;
221
+ height: 41px;
222
+ padding: 0 12px;
223
+ border-bottom: 1px solid var(--color-border);
224
+ }
225
+
226
+ .panel-title {
227
+ font-size: 12px;
228
+ font-weight: 600;
229
+ color: var(--color-muted-foreground);
230
+ }
231
+
232
+ .panel-actions {
233
+ display: flex;
234
+ align-items: center;
235
+ gap: 4px;
236
+ }
237
+
238
+ .panel-body {
239
+ flex: 1;
240
+ min-height: 0;
241
+ overflow-y: auto;
242
+ padding: 8px;
243
+ }
244
+
245
+ /* ------------------------------------------- 底栏(底部居中浮条,与旧版一致) */
246
+
247
+ .app-footer {
248
+ position: fixed;
249
+ /* 居中用 left/right + margin-inline:auto(不再用 transform):
250
+ 宽度是 fit-content,transform 与 auto margin 叠着容易算错中心。 */
251
+ left: 0;
252
+ right: 0;
253
+ bottom: 15px;
254
+ margin-inline: auto;
255
+ /* 宽度由内容决定:控件全部显示、不裁字、不换行;只有真的放不下时才受视口约束 */
256
+ width: fit-content;
257
+ max-width: calc(100vw - 32px);
258
+ /* 高度不再写死:右块(皮肤/动作/重置/背景)在 720px 浮条里放不下一行会换行,
259
+ 写死 42px 会把换行出来的按钮顶出浮条外。min-height 保证单行时仍是原高度。 */
260
+ height: auto;
261
+ min-height: var(--footer-h);
262
+ /* 三块布局用 grid:左右各 1fr、中间 auto ⇒ 中间块天然落在浮条(=视口)正中。
263
+ 不用 position:absolute:那会脱离文档流,窄屏时压住两侧控件。
264
+ 关键:fr 的隐式最小尺寸是 auto(内容最小宽度),内容一宽列就被撑破、右块向左溢出压住
265
+ 中间块(实测皮肤 picker 压住播放按钮)。所以必须用 minmax(0, 1fr) 允许列收缩到 0。 */
266
+ display: grid;
267
+ grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
268
+ align-items: center;
269
+ gap: 8px;
270
+ padding: 0 9px;
271
+ background: var(--color-card);
272
+ border: 1px solid var(--color-border);
273
+ border-radius: var(--radius);
274
+ box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
275
+ z-index: 40;
276
+ }
277
+
278
+ /* ============================================================================
279
+ 影院模式 / 侧栏收起 —— 纯状态类驱动的外观(不含任何 JS)
280
+
281
+ 状态属性(都挂在 <html> 上,JS 只负责切换值,动画细节全在 CSS):
282
+ data-cinema="on" 影院模式:隐藏顶栏与左右侧栏,只留舞台
283
+ data-idle="on" 与 data-cinema="on" 组合:静止数秒后底部播放条淡出
284
+ data-left="off" 收起左栏(角色库)
285
+ data-right="off" 收起右栏(控制面板)
286
+
287
+ 布局说明:顶栏与左右侧栏都是 position:fixed 的浮层,舞台本来就 inset:0 铺满视口,
288
+ 所以上述状态只做位移/透明度过渡,不触发重排 —— 收起侧栏不会留下空白条。
289
+ ========================================================================== */
290
+
291
+ :root {
292
+ /* 统一进出场时长与缓动:影院/侧栏/播放条共用一套,改一处即全局一致 */
293
+ --ui-fade: 200ms;
294
+ --ui-ease: cubic-bezier(0.4, 0, 0.2, 1);
295
+ }
296
+
297
+ /* 顶栏:滑出视口(用 translateY 而不是改 height,避免过渡期间挤压舞台) */
298
+ .app-header {
299
+ transition: transform var(--ui-fade) var(--ui-ease), opacity var(--ui-fade) var(--ui-ease);
300
+ }
301
+
302
+ html[data-cinema="on"] .app-header {
303
+ transform: translateY(-100%);
304
+ opacity: 0;
305
+ pointer-events: none;
306
+ }
307
+
308
+ /* 布局读回完成前,左右栏不参与首次绘制。
309
+ 为什么需要它:HTML 里左右栏默认是"展开"的,而读回偏好(可能上次是收起的)发生在
310
+ ES module 的执行期(异步)——中间那几帧用户会看到"显示 → 又被收起"的闪烁。
311
+ 为什么用 visibility 而不是 display:none:前者仍占位,读回后显示不触发重排(不会整页跳);
312
+ 后者不占位,切回来会重新布局,看起来是"跳一下"。
313
+ 标记由 main.js 在 applyLayout() 之后写入(含异常兜底,不会永久遮住)。
314
+ 还必须关掉过渡:`.panel` 自带 transform/opacity 的 0.2s 过渡,而 applyLayout() 与放行标记
315
+ 在同一个任务里完成 —— visibility 一放开,那条"滑出"过渡就会被真实绘制出来,
316
+ 表现为「先出现、再滑走」。首帧不做过渡,读回落地后的交互照旧有动画。 */
317
+ html:not([data-layout-ready]) #left-panel,
318
+ html:not([data-layout-ready]) #right-panel {
319
+ visibility: hidden;
320
+ }
321
+
322
+ /* 启动期(is-booting)左右栏一律不过渡。
323
+ 为什么需要单独一条:applyLayout() 加的隐藏类(transform/opacity 终态)与"放行可见"
324
+ 若落在同一次样式重算里,浏览器看到的新状态 transition 又是 0.2s ⇒
325
+ 那条"从原位滑到隐藏位"的过渡会被真实画出来 =「先出现、再滑走」。
326
+ 仅靠上一条 :not([data-layout-ready]) 里的 transition:none 管不到这一次变化
327
+ (它只在旧状态生效)。所以启动期用一个独立类把过渡关掉,等隐藏终态落地一帧后再摘掉它。
328
+ 摘掉后:transform 值没变化 ⇒ 不会触发任何过渡;之后的交互切换照旧有 0.2s 动画。 */
329
+ html.is-booting #left-panel,
330
+ html.is-booting #right-panel {
331
+ transition: none !important;
332
+ }
333
+
334
+ /* 左右侧栏:影院模式与"单独收起"合并成同一套位移;
335
+ 与既有 .is-hidden(模型库按钮切换的类)结果一致,两者可叠加不冲突。 */
336
+ html[data-cinema="on"] .panel-left,
337
+ html[data-left="off"] .panel-left,
338
+ .panel-left.is-hidden {
339
+ transform: translateX(-100%);
340
+ opacity: 0;
341
+ pointer-events: none;
342
+ }
343
+
344
+ html[data-cinema="on"] .panel-right,
345
+ html[data-right="off"] .panel-right {
346
+ transform: translateX(100%);
347
+ opacity: 0;
348
+ pointer-events: none;
349
+ }
350
+
351
+ /* 底部播放条:影院模式下整体淡出;静止态(data-idle)完全隐藏并下沉一点,
352
+ 鼠标移动时由 JS 把 data-idle 切回 off 即可平滑回来。 */
353
+ .app-footer {
354
+ transition: opacity var(--ui-fade) var(--ui-ease), transform var(--ui-fade) var(--ui-ease);
355
+ }
356
+
357
+ html[data-cinema="on"][data-idle="on"] .app-footer {
358
+ opacity: 0;
359
+ transform: translateY(8px);
360
+ pointer-events: none;
361
+ }
362
+
363
+ /* 影院模式 + 静止:隐藏鼠标指针,避免光标压在画面里影响观赏 */
364
+ html[data-cinema="on"][data-idle="on"] body {
365
+ cursor: none;
366
+ }
367
+
368
+ /* 影院模式下舞台提示退到最弱,不干扰画面 */
369
+ html[data-cinema="on"] .stage-hint {
370
+ opacity: 0.55;
371
+ }
372
+
373
+ /* ============================================================================
374
+ 两个渲染面互斥:Live2D 激活时隐藏 Spine 画布
375
+ —— 两套渲染栈(Spine WebGL / Live2D 覆盖层)同时可见会叠成一个画面(实测出现过),
376
+ 由 #stage 上的 data-stage-mode 声明当前用哪一面,样式只负责"另一面不可见"。
377
+
378
+ 为什么用 visibility 而不是 display:none:
379
+ display:none 会把元素从布局里摘掉,画布的 offsetWidth/clientWidth 立即变 0,
380
+ 依赖尺寸的 ResizeObserver 会先收到一次"尺寸归零"再收到"恢复",导致 Live2D 覆盖层
381
+ 重算画布尺寸时抖动(闪一下/错位)。visibility:hidden 只改变绘制与命中,
382
+ 布局尺寸、观察器通知都保持不变,切回来时无需重新测量。
383
+ ========================================================================== */
384
+ #stage[data-stage-mode="live2d"] #stage-canvas {
385
+ visibility: hidden;
386
+ }
387
+
388
+ /* 切换中:两个渲染面都遮住,只留中性的加载底色(避免切换瞬间出现两幅模型叠加)。
389
+ 由唯一的切换入口把 data-stage-mode 置为 "loading",切换完成后再落到 spine / live2d。 */
390
+ #stage[data-stage-mode="loading"] #stage-canvas,
391
+ #stage[data-stage-mode="loading"] #live2d-overlay {
392
+ visibility: hidden;
393
+ }
394
+
395
+ /* 兜底:显式处于 spine 模式时,Live2D 覆盖层必须不可见(防止卸载遗漏导致叠加)。
396
+ 用 visibility 而不是 display:保留布局尺寸,避免覆盖层尺寸变化触发 ResizeObserver 抖动。 */
397
+ #stage[data-stage-mode="spine"] #live2d-overlay {
398
+ visibility: hidden;
399
+ }
400
+
401
+ /* ---------------- 覆盖层 / 调试 HUD / 调试日志浮层 ----------------
402
+ 契约《禁止内联 CSS 与 JS》:这些元素的定位与配色是**设计决策**(不是运行时算出来的值),
403
+ 一律走类名 + CSS 变量,JS 只负责加/去类,不再写 element.style。 */
404
+
405
+ /* Live2D 覆盖层:绝对定位铺满舞台,默认隐藏(由 data-stage-mode 或 .is-visible 控制可见性) */
406
+ #live2d-overlay {
407
+ position: absolute;
408
+ left: 0;
409
+ top: 0;
410
+ width: 100%;
411
+ height: 100%;
412
+ z-index: 5;
413
+ display: none;
414
+ /* 事件仍落到下面的 Spine 画布,交互层路径完全不变 */
415
+ pointer-events: none;
416
+ background: transparent;
417
+ overflow: hidden;
418
+ }
419
+ #live2d-overlay.is-visible { display: block; }
420
+ #live2d-overlay-canvas {
421
+ display: block;
422
+ width: 100%;
423
+ height: 100%;
424
+ }
425
+
426
+ /* 渲染诊断 HUD(?diag=1 时才创建) */
427
+ #l2d-diag {
428
+ position: fixed;
429
+ right: 8px;
430
+ bottom: 8px;
431
+ z-index: 99999;
432
+ max-width: 480px;
433
+ padding: 8px 10px;
434
+ border: 1px solid var(--color-border);
435
+ border-radius: 6px;
436
+ background: var(--color-card);
437
+ color: var(--color-foreground);
438
+ font: 11px/1.55 var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
439
+ white-space: pre-wrap;
440
+ box-shadow: 0 4px 16px rgb(0 0 0 / 45%);
441
+ }
442
+ .l2d-diag-head {
443
+ cursor: pointer;
444
+ margin-bottom: 4px;
445
+ color: var(--color-primary);
446
+ user-select: none;
447
+ }
448
+ /* 折叠:整体只藏 body,标题行仍可点 */
449
+ #l2d-diag.is-collapsed .l2d-diag-body { display: none; }
450
+
451
+ /* 调试日志浮层容器(面板内部另有自己的定位) */
452
+ #debug-panel-host {
453
+ position: fixed;
454
+ right: 200px;
455
+ bottom: 100px;
456
+ z-index: 40;
457
+ width: 288px;
458
+ }
459
+
460
+ /* PIXI 画布(Live2D 覆盖层内部):铺满容器、背景透明 */
461
+ .live2d-pixi-canvas {
462
+ display: block;
463
+ width: 100%;
464
+ height: 100%;
465
+ background: transparent;
466
+ }
467
+
468
+ /* 资源根管理行(设置 → 资源根):整行换行布局 */
469
+ .root-row {
470
+ display: flex;
471
+ flex-wrap: wrap;
472
+ gap: 4px;
473
+ align-items: center;
474
+ padding: 4px 0;
475
+ border-bottom: 1px solid var(--color-border);
476
+ }
477
+ .root-name {
478
+ flex: 1 1 100%;
479
+ font-size: 12px;
480
+ }
481
+ .root-path {
482
+ flex: 1 1 100%;
483
+ font-size: 11px;
484
+ word-break: break-all;
485
+ }
486
+ .root-rename-input {
487
+ flex: 1 1 100%;
488
+ }
489
+ /* 删除确认提示:警示色(不写字面色值,走语义令牌) */
490
+ .root-confirm-ask {
491
+ font-size: 11px;
492
+ color: var(--color-danger); /* 语义令牌:删除/危险提示 */
493
+ }
@@ -0,0 +1,121 @@
1
+ // ============================================================================
2
+ // tool/lib/bundle-probe-node.mjs —— 在 Node 里跑插件的「装载前预检」
3
+ //
4
+ // 为什么要这一层:预检实现只有一份(plugins/unity-live2d-bundle/lib/bundle-probe.js),
5
+ // 浏览器由插件 index.js 用,服务端由本文件把同一份 js 读进 vm 沙箱执行。
6
+ // 这样 /api/bundle/probe 与页面里的 probe 结论必然一致,不存在两份解析逻辑。
7
+ //
8
+ // 只加载**数据层**需要的库(不加载 index.js / 渲染相关),首次调用时建沙箱并缓存。
9
+ // ============================================================================
10
+
11
+ import fs from 'node:fs';
12
+ import path from 'node:path';
13
+ import vm from 'node:vm';
14
+ import { fileURLToPath } from 'node:url';
15
+ // 依赖包判据与文案(与前端 plugin-host 共用一份,见 src/shared/bundle-shape.mjs)
16
+ import { histogramFromObjects, isAssetDependencyHistogram, dependencyReason } from '../../src/shared/bundle-shape.mjs';
17
+
18
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
19
+ const ROOT = path.resolve(__dirname, '..', '..');
20
+
21
+ /** 预检用哪个插件的 lib(预检实现只有 unity-live2d-bundle 这一份,spine 复用它) */
22
+ const PROBE_PLUGIN_ID = 'unity-live2d-bundle';
23
+
24
+ /**
25
+ * 取某插件的 lib 目录。
26
+ * @param {string} [pluginId]
27
+ * @returns {string}
28
+ */
29
+ function libDirFor(pluginId = PROBE_PLUGIN_ID) {
30
+ return path.join(ROOT, 'plugins', pluginId, 'lib');
31
+ }
32
+
33
+ /**
34
+ * 从插件的 `plugin.json` 派生「预检要加载的 lib 文件清单」——**唯一真相是 manifest**。
35
+ *
36
+ * 为什么必须派生:这份清单曾经是手写的,与 plugin.json 的 files[] 构成"同一事实的两处表达"。
37
+ * 漏一边的表现极难定位:**浏览器能跑(按 files[] 加载)、服务端预检报缺依赖(按手写清单加载)**。
38
+ * 所以这里不再保留任何文件名常量,只做一件事:按 files[] 顺序取出 `lib/` 前缀项并去掉前缀。
39
+ *
40
+ * 顺序也来自 manifest:files[] 本身就是依赖顺序(errors 最先、bundle-probe 在后),
41
+ * 派生时**原样保留**,不重排(重排就等于又引入一处人工判断)。
42
+ *
43
+ * @param {string} [pluginId]
44
+ * @returns {string[]} 形如 ['errors.js', 'bytes.js', ...]
45
+ */
46
+ export function libOrderFor(pluginId = PROBE_PLUGIN_ID) {
47
+ const manifestPath = path.join(ROOT, 'plugins', pluginId, 'plugin.json');
48
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
49
+ const files = Array.isArray(manifest.files) ? manifest.files : [];
50
+ return files
51
+ .filter((f) => typeof f === 'string' && f.startsWith('lib/'))
52
+ .map((f) => f.slice('lib/'.length));
53
+ }
54
+
55
+ let sandbox = null;
56
+
57
+ /** 建(或取缓存的)库沙箱。首次调用才读文件 + 执行。 */
58
+ function getSandbox() {
59
+ if (sandbox) return sandbox;
60
+ const ctx = {
61
+ console: { log() {}, info() {}, warn() {}, error() {}, debug() {} },
62
+ TextDecoder,
63
+ TextEncoder,
64
+ setTimeout,
65
+ clearTimeout,
66
+ };
67
+ ctx.globalThis = ctx;
68
+ ctx.window = ctx;
69
+ ctx.self = ctx;
70
+ vm.createContext(ctx);
71
+ // 清单在**运行时**从 plugin.json 派生(不是模块加载时算一次):
72
+ // 这样新增 lib 文件后无需改这里,也不会因进程长期驻留而用上过期清单。
73
+ const libDir = libDirFor(PROBE_PLUGIN_ID);
74
+ for (const rel of libOrderFor(PROBE_PLUGIN_ID)) {
75
+ const abs = path.join(libDir, rel);
76
+ // 报错要能自己说清"该看哪两个地方":manifest 声明了但磁盘上没有
77
+ if (!fs.existsSync(abs)) throw new Error(`预检依赖缺失:plugins/${PROBE_PLUGIN_ID}/lib/${rel}(plugin.json 的 files[] 里有它,但磁盘上没有)`);
78
+ // 用 vm 执行而非 import:这些库是浏览器风格的脚本(挂 globalThis.__ULB__),不是 ESM
79
+ vm.runInContext(fs.readFileSync(abs, 'utf8'), ctx, { filename: `ulb-lib/${rel}` });
80
+ }
81
+ sandbox = ctx;
82
+ return sandbox;
83
+ }
84
+
85
+ /**
86
+ * 预检一个资源包字节流。
87
+ * @param {Uint8Array} bytes
88
+ * @returns {object} 与插件内 probe 同源的结论 + 包内类型构成(依赖包判定用)
89
+ */
90
+ export function probeBundleBytes(bytes) {
91
+ const ctx = getSandbox();
92
+ const NS = ctx.__ULB__;
93
+ const bp = NS && NS.bundleProbe;
94
+ const res = (bp && typeof bp.run === 'function') ? bp.run(bytes) : null;
95
+
96
+ // 附加:包内 className 构成 + 是否"美术依赖包"。
97
+ // 判据与文案来自 src/shared/bundle-shape.mjs —— 与前端 plugin-host 用的是同一份,
98
+ // 保证"页面点开"与"直接调 /api/unpack"对同一个包给同一个答案。
99
+ // 这里再解析一次(诊断路径、不进热路径),因为 run() 不暴露对象表。
100
+ let shape = null;
101
+ try {
102
+ const parsed = NS.unityfs.parse(bytes);
103
+ const cab = (parsed.nodes || []).find((n) => /^CAB-/.test(n.path));
104
+ if (cab) {
105
+ const sf = NS.serialized.parse(NS.unityfs.nodeBytes(parsed, cab.path));
106
+ const histogram = histogramFromObjects(sf);
107
+ shape = {
108
+ objectCount: (sf.objects || []).length,
109
+ histogram,
110
+ assetDependency: isAssetDependencyHistogram(histogram),
111
+ };
112
+ if (shape.assetDependency) shape.dependencyReason = dependencyReason(histogram);
113
+ }
114
+ } catch { /* 形态探测失败不影响主结论(探不到就不下依赖包结论) */ }
115
+
116
+ return Object.assign({}, res || {}, shape || {});
117
+ }
118
+
119
+ // 路径与清单都**不再导出成常量**(常量会诱使别人继续手写第二份);
120
+ // 需要自查清单时调 libOrderFor(pluginId) 现取。
121
+ export const PROBE_PATHS = { ROOT, libDirFor, libOrderFor };