modelstage 0.0.0-stage → 0.5.28

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 (281) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +274 -2
  3. package/THIRD_PARTY_NOTICES.md +90 -0
  4. package/bin/modelstage.mjs +137 -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 +63 -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 +654 -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 +396 -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/shared/type-labels.mjs +31 -0
  126. package/src/web/README.md +193 -0
  127. package/src/web/app/anim-list.js +222 -0
  128. package/src/web/app/background-solids.js +150 -0
  129. package/src/web/app/backgrounds.js +152 -0
  130. package/src/web/app/camera-log.js +28 -0
  131. package/src/web/app/context.js +72 -0
  132. package/src/web/app/dir-picker.js +242 -0
  133. package/src/web/app/entry-report.js +71 -0
  134. package/src/web/app/memory-bars.js +91 -0
  135. package/src/web/app/memory-budget.js +228 -0
  136. package/src/web/app/memory-guard.js +204 -0
  137. package/src/web/app/model-actions.js +682 -0
  138. package/src/web/app/renderer-setting.js +70 -0
  139. package/src/web/app/root-manager.js +254 -0
  140. package/src/web/app/status.js +138 -0
  141. package/src/web/app/topbar-actions.js +357 -0
  142. package/src/web/app/transport-actions.js +297 -0
  143. package/src/web/app/ui-state.js +559 -0
  144. package/src/web/app/url-params.js +171 -0
  145. package/src/web/app/view-settings.js +186 -0
  146. package/src/web/components.css +524 -0
  147. package/src/web/debug/diag-hud.js +95 -0
  148. package/src/web/debug/drag.js +369 -0
  149. package/src/web/debug/log.js +320 -0
  150. package/src/web/debug/model-cmp.js +292 -0
  151. package/src/web/debug/panel.css +474 -0
  152. package/src/web/debug/panel.js +712 -0
  153. package/src/web/debug/race-probe.js +82 -0
  154. package/src/web/debug/upload.js +130 -0
  155. package/src/web/engine/adapters/atlas.js +47 -0
  156. package/src/web/engine/adapters/index.js +22 -0
  157. package/src/web/engine/adapters/renderer.js +223 -0
  158. package/src/web/engine/adapters/skeleton.js +105 -0
  159. package/src/web/engine/adapters/texture.js +64 -0
  160. package/src/web/engine/animation.js +181 -0
  161. package/src/web/engine/appearance.js +136 -0
  162. package/src/web/engine/camera-adapter.js +212 -0
  163. package/src/web/engine/effect-chain.js +138 -0
  164. package/src/web/engine/engine-state.js +120 -0
  165. package/src/web/engine/filters.js +224 -0
  166. package/src/web/engine/fit.js +197 -0
  167. package/src/web/engine/frame-gate.js +33 -0
  168. package/src/web/engine/gl-driver.js +56 -0
  169. package/src/web/engine/gl.js +77 -0
  170. package/src/web/engine/index.js +20 -0
  171. package/src/web/engine/loader.js +55 -0
  172. package/src/web/engine/model-info.js +26 -0
  173. package/src/web/engine/pipeline.js +200 -0
  174. package/src/web/engine/player.js +427 -0
  175. package/src/web/engine/pma/blend.js +76 -0
  176. package/src/web/engine/pma/detect.js +118 -0
  177. package/src/web/engine/pma/index.js +8 -0
  178. package/src/web/engine/pma/resolve.js +112 -0
  179. package/src/web/engine/pma/upload.js +127 -0
  180. package/src/web/engine/pma.js +5 -0
  181. package/src/web/engine/render-frame.js +93 -0
  182. package/src/web/engine/runtime-capabilities.js +209 -0
  183. package/src/web/engine/runtime.js +37 -0
  184. package/src/web/engine/skins.js +165 -0
  185. package/src/web/engine/textures.js +174 -0
  186. package/src/web/index.html +117 -0
  187. package/src/web/infra/api.js +531 -0
  188. package/src/web/infra/diag.js +509 -0
  189. package/src/web/infra/document.js +269 -0
  190. package/src/web/infra/dom.js +31 -0
  191. package/src/web/infra/events.mjs +27 -0
  192. package/src/web/infra/log.js +153 -0
  193. package/src/web/infra/mem-stats.js +112 -0
  194. package/src/web/infra/plugin-host-registration.mjs +285 -0
  195. package/src/web/infra/plugin-host-report.mjs +32 -0
  196. package/src/web/infra/plugin-host-unload.mjs +79 -0
  197. package/src/web/infra/plugin-host-unpack.mjs +899 -0
  198. package/src/web/infra/plugin-host-warmup.mjs +180 -0
  199. package/src/web/infra/plugin-host.js +628 -0
  200. package/src/web/infra/ready.js +461 -0
  201. package/src/web/infra/schedule.js +51 -0
  202. package/src/web/infra/shortcuts.js +129 -0
  203. package/src/web/infra/slot-registry.js +402 -0
  204. package/src/web/infra/vfs.js +247 -0
  205. package/src/web/live2d/bridge.js +400 -0
  206. package/src/web/live2d/model.js +321 -0
  207. package/src/web/live2d/overlay.js +142 -0
  208. package/src/web/live2d/pixi-app.js +111 -0
  209. package/src/web/live2d/playback-probe.js +92 -0
  210. package/src/web/live2d/probe-geometry.js +329 -0
  211. package/src/web/live2d/probe.js +119 -0
  212. package/src/web/live2d/release.js +66 -0
  213. package/src/web/live2d/runtime-adapter.js +289 -0
  214. package/src/web/live2d/stage-ctx.js +152 -0
  215. package/src/web/live2d/stage.js +204 -0
  216. package/src/web/live2d/vfs-settings.js +174 -0
  217. package/src/web/live2d/view.js +89 -0
  218. package/src/web/main.js +629 -0
  219. package/src/web/panel/background-list.js +373 -0
  220. package/src/web/panel/bg-source.js +291 -0
  221. package/src/web/panel/color-picker.css +199 -0
  222. package/src/web/panel/color-picker.js +278 -0
  223. package/src/web/panel/dialog.css +116 -0
  224. package/src/web/panel/dialog.js +173 -0
  225. package/src/web/panel/dom.js +35 -0
  226. package/src/web/panel/icons.js +58 -0
  227. package/src/web/panel/memory-bar.js +195 -0
  228. package/src/web/panel/panel-settings.js +1263 -0
  229. package/src/web/panel/panel.css +1304 -0
  230. package/src/web/panel/render-choice.js +104 -0
  231. package/src/web/panel/schema.js +784 -0
  232. package/src/web/panel/settings-io.js +291 -0
  233. package/src/web/panel/shell-controls.js +274 -0
  234. package/src/web/panel/slider.js +210 -0
  235. package/src/web/panel/toolbar-schema.js +111 -0
  236. package/src/web/panel/toolbar.js +195 -0
  237. package/src/web/panel/transport-picker.js +375 -0
  238. package/src/web/panel/transport.js +321 -0
  239. package/src/web/panel/view.js +553 -0
  240. package/src/web/plugin/unpack-worker.js +185 -0
  241. package/src/web/render/camera/constants.js +60 -0
  242. package/src/web/render/camera/index.js +123 -0
  243. package/src/web/render/camera/spine.js +124 -0
  244. package/src/web/render/capture.js +30 -0
  245. package/src/web/render/interaction.js +527 -0
  246. package/src/web/render/live2d.js +627 -0
  247. package/src/web/render/playback.js +47 -0
  248. package/src/web/render/reset-view.js +29 -0
  249. package/src/web/render/spine.js +386 -0
  250. package/src/web/render/surface-switch.js +176 -0
  251. package/src/web/render/surface.js +571 -0
  252. package/src/web/render/surfaces.js +70 -0
  253. package/src/web/roster/filter.js +31 -0
  254. package/src/web/roster/list.js +276 -0
  255. package/src/web/roster/persist.js +92 -0
  256. package/src/web/roster/reveal.js +77 -0
  257. package/src/web/roster/rows.js +248 -0
  258. package/src/web/roster/snapshot.js +39 -0
  259. package/src/web/roster/type-filter.js +97 -0
  260. package/src/web/roster/view.js +761 -0
  261. package/src/web/spine/canvas-api-groups.js +267 -0
  262. package/src/web/spine/canvas-api.js +97 -0
  263. package/src/web/spine/canvas-document.js +80 -0
  264. package/src/web/spine/canvas-engine.js +193 -0
  265. package/src/web/spine/canvas-entry.js +28 -0
  266. package/src/web/spine/canvas-pixels.js +36 -0
  267. package/src/web/spine/canvas-runtime.js +127 -0
  268. package/src/web/spine/canvas-view.js +108 -0
  269. package/src/web/spine/canvas.js +7 -0
  270. package/src/web/spine/runtime-hint.js +73 -0
  271. package/src/web/spine/runtimes.js +212 -0
  272. package/src/web/style.css +493 -0
  273. package/tool/lib/bundle-probe-node.mjs +121 -0
  274. package/tool/lib/paths.mjs +20 -0
  275. package/tool/lib/runtime-guides.mjs +65 -0
  276. package/tool/lib/runtime-package.mjs +403 -0
  277. package/tool/lib/spine-meta.mjs +227 -0
  278. package/tool/lib/spine-motions.mjs +104 -0
  279. package/tool/parse-jsonc.mjs +86 -0
  280. package/tool/resolve-live2d-runtimes.mjs +276 -0
  281. package/tool/spine-motion-rules.json +101 -0
@@ -0,0 +1,524 @@
1
+ /* 语义色 token:控件外观用到的语义色集中在这里定义(基础调色板在 style.css)。
2
+ 抽成变量是为了换主题/深浅色时只改一处,而不是在十几处裸色值里逐个找。 */
3
+ :root {
4
+ --color-accent-action: #2b6cb0; /* 主操作按钮底色 */
5
+ --color-accent-action-hover: #3178c6;
6
+ --color-on-accent: #fff; /* 主色/类型标签上的文字 */
7
+ /* 变体标签颜色**不在这里定义**:由插件在 plugin.json 里自声明 accent,
8
+ 运行期以内联变量 --badge-accent 落到该元素上(见 roster/rows.js)。
9
+ 这里只留一个"判不出归属"的中性兜底色,避免没声明的插件被涂成别人的颜色。 */
10
+ --color-type-unknown: #4a5568; /* 变体标签:归属待定/未声明 */
11
+ --color-text-strong: #cfd6dd; /* 变体行主文本 */
12
+ --color-text-dim: #9aa; /* 展开箭头等弱提示 */
13
+ --color-orig-border: #4a5; /* 角色行「原名」标签描边 */
14
+ --color-orig-text: #8a9; /* 「原名」/计数字样 */
15
+ --color-icon-bg: #111; /* 角色头像占位底 */
16
+ --color-text-muted: #8a94a0; /* 变体行原名后缀等次级文本 */
17
+ }
18
+
19
+ /* ============================================================================
20
+ ModelStage 本实现 · 控件外观
21
+ 只管"控件长什么样":品牌、按钮、资源根列表、右栏分组行、底栏分组、错误横幅。
22
+ 布局与几何在 style.css,两个文件由 index.html 一起引入。
23
+ ========================================================================== */
24
+
25
+ /* ------------------------------------------------------------ 品牌(顶栏左) */
26
+
27
+ .brand {
28
+ display: flex;
29
+ align-items: center;
30
+ gap: 8px;
31
+ min-width: 0;
32
+ }
33
+
34
+ .brand-icon {
35
+ display: inline-flex;
36
+ align-items: center;
37
+ justify-content: center;
38
+ width: 16px;
39
+ height: 16px;
40
+ color: var(--color-primary);
41
+ font-size: 15px;
42
+ line-height: 1;
43
+ }
44
+
45
+ /* 内置 SVG(默认形态):颜色跟随 .brand-icon 的 color */
46
+ .brand-icon svg {
47
+ width: 16px;
48
+ height: 16px;
49
+ display: block;
50
+ }
51
+
52
+ /* 服务端下发图片图标时的 <img> */
53
+ .brand-icon.is-image img {
54
+ width: 18px;
55
+ height: 18px;
56
+ object-fit: contain;
57
+ display: block;
58
+ }
59
+
60
+ .brand-title {
61
+ font-size: 13px;
62
+ font-weight: 500;
63
+ white-space: nowrap;
64
+ overflow: hidden;
65
+ text-overflow: ellipsis;
66
+ }
67
+
68
+ /* ---------------------------------------------------------------- 按钮 */
69
+
70
+ .header-actions {
71
+ display: flex;
72
+ align-items: center;
73
+ gap: 6px;
74
+ /* 内置 5 个按钮固定可见;插件按钮多时只让滚动区压缩,不挤动内置按钮 */
75
+ min-width: 0;
76
+ }
77
+
78
+ /* 插件按钮区:横向滚动、不换行(换行会把顶栏撑高)。
79
+ 为什么给它上限宽度:顶栏是固定高度的一条,插件按钮无限增长必须有个"先滚起来"的边界;
80
+ 取 min(42vw,460px) 是"常见分辨率下能放下 3~5 个、再多就滚"的折中。 */
81
+ .header-plugin-actions {
82
+ display: flex;
83
+ /* 顶端对齐:滚动条在内容下方,若居中会把按钮整体抬高 2~3px(与内置按钮错位) */
84
+ align-items: flex-start;
85
+ gap: 6px;
86
+ flex-wrap: nowrap;
87
+ min-width: 0;
88
+ /* 上限取 min(30vw,320px):实测 8 个按钮内容宽约 362px ⇒ 溢出并由容器滚动;
89
+ 常见的 2~4 个按钮(约 80~160px)远小于上限,不会出现滚动条。 */
90
+ max-width: min(30vw, 320px);
91
+ /* 高度锁死成按钮高度(30px):插件按钮与内置按钮必须同一条基线。
92
+ 滚动条会占 6px —— 用 margin-bottom:-6px 把这 6px 抵消掉,
93
+ 否则整条顶栏(nav)会被撑高、按钮也随之向上错位(实测 30px → 40px、错位 5px)。 */
94
+ /* 30px 按钮区 + 6px 滚动条 = 36px 内容高;只把**下边距**做成 -6px:
95
+ 净高度回到 30px(nav 不被撑高),而顶端仍与内置按钮同一条基线。
96
+ 上下对称的 -3px 会把整块上移 3px(实测插件按钮比内置高 3px)。 */
97
+ height: 36px;
98
+ margin-top: 0;
99
+ margin-bottom: -6px;
100
+ overflow-x: auto;
101
+ overflow-y: hidden;
102
+ overscroll-behavior-x: contain;
103
+ /* 滚动条:细、默认透明,悬停才显形(与整体克制风格一致) */
104
+ scrollbar-width: thin;
105
+ scrollbar-color: transparent transparent;
106
+ }
107
+
108
+ .header-plugin-actions:hover {
109
+ scrollbar-color: var(--color-border) transparent;
110
+ }
111
+
112
+ .header-plugin-actions::-webkit-scrollbar {
113
+ height: 6px;
114
+ }
115
+
116
+ .header-plugin-actions::-webkit-scrollbar-track {
117
+ background: transparent;
118
+ }
119
+
120
+ .header-plugin-actions::-webkit-scrollbar-thumb {
121
+ background: transparent;
122
+ border-radius: 3px;
123
+ }
124
+
125
+ .header-plugin-actions:hover::-webkit-scrollbar-thumb {
126
+ background: var(--color-border);
127
+ }
128
+
129
+ /* 按钮自身不压缩:滚动由容器负责,按钮保持方形/文字宽 */
130
+ .header-plugin-actions > .icon-btn {
131
+ flex: 0 0 auto;
132
+ }
133
+
134
+ .icon-btn {
135
+ height: 30px;
136
+ padding: 0 10px;
137
+ display: inline-flex;
138
+ align-items: center;
139
+ border: 1px solid transparent;
140
+ border-radius: 6px;
141
+ color: var(--color-foreground);
142
+ font-size: 12px;
143
+ white-space: nowrap;
144
+ }
145
+
146
+ .icon-btn:hover {
147
+ background: var(--color-secondary);
148
+ border-color: var(--color-border);
149
+ }
150
+
151
+ /* 图标版顶栏按钮:方形、只放图标,名称走 aria-label/title */
152
+ .icon-btn:has(> svg) {
153
+ width: 30px;
154
+ padding: 0;
155
+ justify-content: center;
156
+ }
157
+
158
+ .btn-icon {
159
+ display: inline-flex;
160
+ align-items: center;
161
+ margin-right: 4px;
162
+ }
163
+
164
+ .btn-icon > svg {
165
+ width: 14px;
166
+ height: 14px;
167
+ display: block;
168
+ }
169
+
170
+ .mini-btn.has-icon,
171
+ .transport-group .mini-btn.has-icon {
172
+ display: inline-flex;
173
+ align-items: center;
174
+ }
175
+
176
+ .icon-btn > svg {
177
+ width: 17px;
178
+ height: 17px;
179
+ display: block;
180
+ }
181
+
182
+ /* 选中/开启态(模型库、菜单、影院这类开关型按钮):主色描边 + 主色图标 */
183
+ .icon-btn[aria-pressed='true'] {
184
+ color: var(--color-primary);
185
+ border-color: color-mix(in srgb, var(--color-primary) 45%, transparent);
186
+ }
187
+
188
+ .icon-btn[aria-pressed='false'] {
189
+ color: var(--color-muted-foreground);
190
+ }
191
+
192
+ .mini-btn {
193
+ height: 26px;
194
+ padding: 0 8px;
195
+ border: 1px solid var(--color-border);
196
+ border-radius: 6px;
197
+ background: var(--color-muted);
198
+ color: var(--color-foreground);
199
+ font-size: 12px;
200
+ white-space: nowrap;
201
+ }
202
+
203
+ .mini-btn:hover:not(:disabled) {
204
+ background: var(--color-secondary);
205
+ }
206
+
207
+ /* 主操作按钮(左栏「选择目录」):配色*/
208
+ .mini-btn.is-primary {
209
+ border-color: transparent;
210
+ background: var(--color-accent-action);
211
+ color: var(--color-on-accent);
212
+ }
213
+
214
+ .mini-btn.is-primary:hover:not(:disabled) {
215
+ background: var(--color-accent-action-hover);
216
+ }
217
+
218
+ /* 强调按钮(**描边式**):表示"这一屏的主操作",但不做高饱和实心。
219
+ 为什么不用 .is-primary:它是实心高饱和 + 反白字,在暗色描边面板/对话框里像另一套设计。
220
+ 应用处:左栏「选择目录」、目录对话框「添加」、对话框内「此目录已选中」——
221
+ 三处必须看起来是同一套按钮,所以只允许这一个类,不许各自加样式。 */
222
+ .mini-btn.is-accent {
223
+ border-color: var(--color-primary);
224
+ color: var(--color-primary);
225
+ background: color-mix(in srgb, var(--color-primary) 14%, transparent);
226
+ font-weight: 600;
227
+ }
228
+
229
+ .mini-btn.is-accent:hover:not(:disabled) {
230
+ border-color: var(--color-primary);
231
+ background: color-mix(in srgb, var(--color-primary) 22%, transparent);
232
+ }
233
+
234
+ /* ------------------------------------------------------------ 提示文案 */
235
+
236
+ .muted-hint {
237
+ margin: 8px 4px;
238
+ color: var(--color-muted-foreground);
239
+ font-size: 11px;
240
+ line-height: 1.6;
241
+ }
242
+
243
+ /* ------------------------------------------------ 角色库:计数 / 搜索 / 三级树 */
244
+
245
+ .panel-count {
246
+ flex: none;
247
+ padding: 6px 12px 0;
248
+ color: var(--color-muted-foreground);
249
+ font-size: 11px;
250
+ line-height: 1.4;
251
+ }
252
+
253
+ .panel-search {
254
+ flex: none;
255
+ padding: 6px 12px 8px;
256
+ }
257
+
258
+ .panel-search input {
259
+ width: 100%;
260
+ box-sizing: border-box;
261
+ padding: 5px 8px;
262
+ border: 1px solid var(--color-border);
263
+ border-radius: 6px;
264
+ background: var(--color-muted);
265
+ color: var(--color-foreground);
266
+ font-size: 12px;
267
+ outline: none;
268
+ }
269
+
270
+ .panel-search input:focus {
271
+ border-color: var(--color-primary);
272
+ }
273
+
274
+ /* ------------------------------------------------------------ 类型筛选胶囊行 */
275
+
276
+ /**
277
+ * 类型胶囊行(搜索框与名册列表之间)。
278
+ *
279
+ * 两个必须的几何约束(设计稿 §4.1 与 §10.2):
280
+ * ① **自动换行、不横向滚动**:类型数量由数据决定(今天 3 种,将来可能更多),
281
+ * 横向滚动在 304px 的窄栏里几乎不可用;
282
+ * ② **单行最小高度**:类型从 2 行回落到 1 行时,若容器随之变矮,下面的列表会"猛地往上一跳"。
283
+ * 给 min-height 兜住这一档,高度变化只发生在真的加了一整行时。
284
+ */
285
+ .roster-type-filter {
286
+ flex: none;
287
+ display: flex;
288
+ flex-wrap: wrap;
289
+ gap: 6px;
290
+ min-height: 22px; /* 单行高度:防列表因胶囊行消失/收缩而跳动 */
291
+ padding: 0 12px 8px;
292
+ }
293
+
294
+ .roster-type-filter[hidden] {
295
+ display: none;
296
+ }
297
+
298
+ /* 单个类型胶囊:复用 .mini-btn 的视觉语言(描边 + 底色),但高度更小、可换行 */
299
+ .roster-type-chip,
300
+ .roster-type-clear {
301
+ height: 22px;
302
+ padding: 0 8px;
303
+ border: 1px solid var(--color-border);
304
+ border-radius: 999px;
305
+ background: var(--color-muted);
306
+ color: var(--color-foreground);
307
+ font-size: 12px;
308
+ line-height: 20px;
309
+ white-space: nowrap;
310
+ cursor: pointer;
311
+ }
312
+
313
+ .roster-type-chip:hover,
314
+ .roster-type-clear:hover {
315
+ background: var(--color-secondary);
316
+ }
317
+
318
+ /* 选中态:主色描边 + 主色底(与工具栏 aria-pressed 同一套语义,颜色浓度略低以免抢眼) */
319
+ .roster-type-chip[aria-pressed='true'] {
320
+ border-color: var(--color-primary);
321
+ color: var(--color-primary);
322
+ background: color-mix(in srgb, var(--color-primary) 18%, transparent);
323
+ font-weight: 600;
324
+ }
325
+
326
+ /* 「清除」比胶囊更轻:它是辅助动作,不该和类型胶囊抢视觉权重 */
327
+ .roster-type-clear {
328
+ border-style: dashed;
329
+ color: var(--color-muted-foreground, var(--color-foreground));
330
+ opacity: 0.85;
331
+ }
332
+
333
+ .roster-list {
334
+ padding: 2px 0 8px;
335
+ }
336
+
337
+ /* 树行通用:一行 flex,靠 padding-left 表达层级 */
338
+ .tree-row {
339
+ display: flex;
340
+ align-items: center;
341
+ gap: 6px;
342
+ padding: 4px 10px;
343
+ cursor: pointer;
344
+ user-select: none;
345
+ }
346
+
347
+ .tree-row:hover {
348
+ background: var(--color-secondary);
349
+ }
350
+
351
+ .tree-game {
352
+ padding-top: 6px;
353
+ padding-bottom: 6px;
354
+ font-weight: 600;
355
+ }
356
+
357
+ .tree-char {
358
+ padding-left: 22px;
359
+ }
360
+
361
+ .tree-variant {
362
+ padding-left: 40px;
363
+ color: var(--color-text-strong);
364
+ }
365
+
366
+ /* 展开箭头(内联 SVG):不依赖字体,避免无桌面字体环境渲染成豆腐块 */
367
+ .tree-arrow {
368
+ flex: 0 0 10px;
369
+ width: 10px;
370
+ height: 10px;
371
+ color: var(--color-text-dim);
372
+ }
373
+
374
+ .tree-game-name {
375
+ flex: 1;
376
+ min-width: 0;
377
+ overflow: hidden;
378
+ white-space: nowrap;
379
+ text-overflow: ellipsis;
380
+ }
381
+
382
+ /* 资源根 id 徽标:区分同名资源根(如碧蓝的三个根) */
383
+ .tree-badge {
384
+ flex: 0 0 auto;
385
+ padding: 0 5px;
386
+ border: 1px solid var(--color-border);
387
+ border-radius: 4px;
388
+ color: var(--color-primary);
389
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
390
+ font-size: 10px;
391
+ font-weight: 400;
392
+ }
393
+
394
+ .tree-icon {
395
+ flex: 0 0 26px;
396
+ width: 26px;
397
+ height: 26px;
398
+ object-fit: contain;
399
+ border-radius: 4px;
400
+ background: var(--color-icon-bg);
401
+ }
402
+
403
+ .tree-char-title {
404
+ flex: 1;
405
+ min-width: 0;
406
+ overflow: hidden;
407
+ white-space: nowrap;
408
+ text-overflow: ellipsis;
409
+ }
410
+
411
+ /* 角色行的「原名」标签(归一化键,与显示名不同才有信息增量) */
412
+ .tree-orig {
413
+ flex: 0 0 auto;
414
+ padding: 0 3px;
415
+ border: 1px solid var(--color-orig-border);
416
+ border-radius: 3px;
417
+ color: var(--color-orig-text);
418
+ font-size: 10px;
419
+ font-weight: 400;
420
+ }
421
+
422
+ .tree-count {
423
+ flex: 0 0 auto;
424
+ color: var(--color-orig-text);
425
+ font-size: 11px;
426
+ font-weight: 400;
427
+ }
428
+
429
+ /* 未匹配名表的角色:右侧一个小红点(hover 有说明)。
430
+ 为什么是一个点而不是文字/图标:名册是用户界面,红点只表达"这里没匹配上、值得看一眼";
431
+ 未命中通常是少数几条,做成常驻文字会把内部概念铺满整个列表。 */
432
+ .tree-miss-dot {
433
+ flex: 0 0 auto;
434
+ width: 6px;
435
+ height: 6px;
436
+ margin-left: 2px;
437
+ border-radius: 50%;
438
+ background: var(--color-danger, #e05252);
439
+ cursor: help;
440
+ }
441
+
442
+ /* 变体行的类型标签:颜色**由插件声明**(内联 --badge-accent,来自 plugin.json 的 accent)。
443
+ 这里不再有 kind → 颜色的映射:拿不到声明就落中性色,绝不替插件假装一种颜色。
444
+ (类名 tree-type-spine / -live2d / -bundle 仍由 roster/rows.js 打上,只作分组用,不再上色) */
445
+ .tree-type {
446
+ flex: 0 0 auto;
447
+ padding: 0 5px;
448
+ border-radius: 4px;
449
+ background: var(--badge-accent, var(--color-type-unknown));
450
+ color: var(--color-on-accent);
451
+ font-size: 10px;
452
+ }
453
+
454
+ .tree-variant-text {
455
+ flex: 0 1 auto;
456
+ min-width: 0;
457
+ overflow: hidden;
458
+ white-space: nowrap;
459
+ text-overflow: ellipsis;
460
+ }
461
+
462
+ .tree-variant-orig {
463
+ flex: 0 1 auto;
464
+ color: var(--color-text-muted);
465
+ font-size: 11px;
466
+ white-space: nowrap;
467
+ overflow: hidden;
468
+ text-overflow: ellipsis;
469
+ }
470
+
471
+ /* 选中态:变体行高亮 */
472
+ .tree-variant.is-selected {
473
+ background: rgba(31, 192, 197, 0.14);
474
+ box-shadow: inset 2px 0 0 var(--color-primary);
475
+ color: var(--color-primary);
476
+ }
477
+
478
+ .tree-hint {
479
+ padding-left: 22px;
480
+ }
481
+
482
+ /* --------------------------------------------------------- 底栏三组按钮 */
483
+
484
+ .transport-group {
485
+ display: flex;
486
+ align-items: center;
487
+ gap: 6px;
488
+ min-width: 0;
489
+ }
490
+
491
+ .transport-group:first-child {
492
+ justify-content: flex-start;
493
+ }
494
+
495
+ .transport-group:last-child {
496
+ justify-content: flex-end;
497
+ }
498
+
499
+ /* ----------------------------------------------------------- 错误横幅 */
500
+
501
+ .error-banner {
502
+ margin: 8px 4px;
503
+ padding: 8px 10px;
504
+ border: 1px solid var(--color-danger);
505
+ border-radius: 6px;
506
+ background: rgba(224, 108, 117, 0.12);
507
+ color: var(--color-danger);
508
+ font-size: 11px;
509
+ line-height: 1.5;
510
+ word-break: break-all;
511
+ }
512
+
513
+ /* 资产残缺角标(catalog 里 missingTextures 非空的条目)
514
+ 为什么显示而不是隐藏:这些条目点了会渲染失败,用户有权知道"为什么这个不行",
515
+ 并且有资源包版本时系统会自动改用(状态行会明确提示,不静默降级)。 */
516
+ .tree-variant-warn {
517
+ color: #e8a33d;
518
+ margin-left: 4px;
519
+ font-size: 12px;
520
+ flex: none;
521
+ }
522
+ .tree-variant.is-incomplete .tree-variant-text {
523
+ opacity: 0.72;
524
+ }
@@ -0,0 +1,95 @@
1
+ // diag-hud.js —— 渲染诊断 HUD(仅 URL 带 `?diag=1` 时创建)
2
+ //
3
+ // 为什么做成独立模块:诊断面板只读、零依赖、且不在正常路径上;
4
+ // 单独一个文件便于审查「它绝不参与渲染决策」,也便于将来整文件删除。
5
+ //
6
+ // 用法(在 createLive2DStage 内):
7
+ // installDiagHud({ getRendererInfo: rendererInfo, readCanvasPixels, overlayCanvasId });
8
+ // 行为:不带 `diag=1` ⇒ 什么都不创建;带 ⇒ 右下角面板,每 500ms 刷新。
9
+
10
+ /**
11
+ * 安装诊断 HUD。
12
+ * @param {{
13
+ * getRendererInfo: () => object|null,
14
+ * readCanvasPixels: (points: Array<[number, number]>) => Promise<Array<object>|null>,
15
+ * overlayCanvasId: string,
16
+ * log?: (message: string) => void,
17
+ * }} options
18
+ */
19
+ export function installDiagHud({ getRendererInfo, readCanvasPixels, overlayCanvasId, log } = {}) {
20
+ try {
21
+ if (typeof document === 'undefined') return;
22
+ // 只认 diag=1;其它参数一律不启用(对正常渲染零影响)
23
+ const enabled = /[?&]diag=1(?:&|$)/.test(window.location.search || '');
24
+ if (!enabled) return { stop() {} };
25
+ if (document.getElementById('l2d-diag')) return;
26
+
27
+ const box = document.createElement('div');
28
+ box.id = 'l2d-diag';
29
+ // 样式改由 style.css 的 #l2d-diag 承担(契约:设计值不进 JS)
30
+
31
+ const head = document.createElement('div');
32
+ head.className = 'l2d-diag-head';
33
+ head.textContent = '【渲染诊断】点此折叠';
34
+ const body = document.createElement('div');
35
+ body.className = 'l2d-diag-body';
36
+ box.appendChild(head);
37
+ box.appendChild(body);
38
+ document.body.appendChild(box);
39
+
40
+ let collapsed = false;
41
+ head.addEventListener('click', () => {
42
+ collapsed = !collapsed;
43
+ box.classList.toggle('is-collapsed', collapsed); // 折叠态交给 CSS
44
+ head.textContent = collapsed ? '【渲染诊断】已折叠(点此展开)' : '【渲染诊断】点此折叠';
45
+ });
46
+
47
+ /** 尺寸描述:attr / client */
48
+ const sizes = (el) => (el ? `${el.width}x${el.height} / ${el.clientWidth}x${el.clientHeight}` : 'n/a');
49
+
50
+ const update = async () => {
51
+ if (collapsed) return;
52
+ try {
53
+ const stageEl = document.getElementById('stage');
54
+ const spineCv = document.getElementById('stage-canvas');
55
+ const overlayCv = document.getElementById(overlayCanvasId);
56
+ const ri = (() => {
57
+ try { return getRendererInfo ? getRendererInfo() : null; } catch { return null; }
58
+ })();
59
+ if (!ri) {
60
+ body.textContent = `等待模型…(app 尚未就绪)
61
+ viewport ${window.innerWidth}x${window.innerHeight}`;
62
+ return;
63
+ }
64
+ const rows = [
65
+ `viewport ${window.innerWidth}x${window.innerHeight}`,
66
+ `stageMode ${stageEl ? stageEl.dataset.stageMode || 'n/a' : 'n/a'} #stage.bg ${stageEl ? getComputedStyle(stageEl).backgroundColor : 'n/a'}`,
67
+ `#stage-canvas attr/client ${sizes(spineCv)} visibility ${spineCv ? getComputedStyle(spineCv).visibility : 'n/a'}`,
68
+ `#live2d-overlay attr/client ${sizes(overlayCv)} bg ${overlayCv ? getComputedStyle(overlayCv).backgroundColor : 'n/a'}`,
69
+ `PIXI w ${ri ? ri.width : '?'} h ${ri ? ri.height : '?'} res ${ri ? ri.resolution : '?'}`,
70
+ ];
71
+ if (overlayCv && overlayCv.clientWidth > 0 && overlayCv.clientHeight > 0 && readCanvasPixels) {
72
+ const w = overlayCv.clientWidth;
73
+ const h = overlayCv.clientHeight;
74
+ const px = await readCanvasPixels([[4, 4], [w - 4, 4], [4, h - 4], [w - 4, h - 4], [Math.floor(w / 2), Math.floor(h / 2)]]);
75
+ if (px) {
76
+ const alphaAt = (i) => (px[i] && px[i].rgba ? px[i].rgba[3] : '?');
77
+ rows.push(`calls ${ri?.clearCalls ?? '?'} / bufFv ${ri?.clearBufferCalls?.fv ?? '?'} / bufFi ${ri?.clearBufferCalls?.fi ?? '?'} / wrMask ${(ri?.colorWriteMask || []).map((v) => (v ? 1 : 0)).join('') || '?'} / ctxAlpha ${ri?.contextAlpha === true ? 1 : ri?.contextAlpha === false ? 0 : '?'}`);
78
+ }
79
+ }
80
+ body.textContent = rows.join('\n');
81
+ } catch (err) {
82
+ body.textContent = `diag error: ${err && err.message ? err.message : err}`;
83
+ }
84
+ };
85
+
86
+ update();
87
+ const timer = setInterval(() => { try { update(); } catch (e) { if (window.console && console.warn) console.warn('[diag-hud]', e && e.message); } }, 500);
88
+ return { stop() { try { clearInterval(timer); } catch { /* 忽略 */ } } };
89
+ if (typeof log === 'function') log('诊断 HUD 已启用(?diag=1)');
90
+ } catch {
91
+ /* 诊断失败不影响渲染 */
92
+ }
93
+ }
94
+
95
+ export default installDiagHud;