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,1304 @@
1
+ /* ============================================================================
2
+ ModelStage 本实现 · 右侧控制面板 + 底部播放条样式(M3)
3
+ 只管这两个区域内部的样子:手风琴、分段按钮、开关、占位说明、播放条控件。
4
+ 区域几何(右栏贴右、播放条居中)仍在 style.css,不在这里重复。
5
+ 配色沿用 style.css 的变量(--color-card / --color-border / --color-primary …)。
6
+ ========================================================================== */
7
+
8
+ /* --------------------------------------------------------------- 手风琴 */
9
+
10
+ .acc {
11
+ border-bottom: 1px solid rgba(53, 58, 62, 0.5);
12
+ }
13
+
14
+ .acc-trigger > .btn-icon > svg {
15
+ width: 15px;
16
+ height: 15px;
17
+ color: var(--color-primary);
18
+ }
19
+
20
+ .acc-trigger {
21
+ width: 100%;
22
+ display: flex;
23
+ align-items: center;
24
+ gap: 6px;
25
+ padding: 8px 6px;
26
+ color: var(--color-foreground);
27
+ font-size: 12px;
28
+ text-align: left;
29
+ background: transparent;
30
+ cursor: pointer;
31
+ }
32
+
33
+ .acc-trigger:hover {
34
+ background: var(--color-secondary);
35
+ }
36
+
37
+ /* 箭头:CSS 画三角(不依赖字体),展开时转向下 */
38
+ .acc-arrow {
39
+ flex: 0 0 8px;
40
+ width: 8px;
41
+ height: 8px;
42
+ border-top: 1.5px solid currentColor;
43
+ border-right: 1.5px solid currentColor;
44
+ transform: rotate(45deg);
45
+ transition: transform 0.15s ease;
46
+ opacity: 0.75;
47
+ }
48
+
49
+ .acc-trigger[aria-expanded='true'] .acc-arrow {
50
+ transform: rotate(135deg);
51
+ }
52
+
53
+ .acc-title {
54
+ flex: 1;
55
+ min-width: 0;
56
+ overflow: hidden;
57
+ white-space: nowrap;
58
+ text-overflow: ellipsis;
59
+ }
60
+
61
+ .acc-body {
62
+ padding: 2px 6px 8px;
63
+ display: flex;
64
+ flex-direction: column;
65
+ gap: 6px;
66
+ }
67
+
68
+ .acc-body[hidden] {
69
+ display: none;
70
+ }
71
+
72
+ /* --------------------------------------------------------- 控件公共行 */
73
+
74
+ .ctl-row {
75
+ display: flex;
76
+ flex-direction: column;
77
+ gap: 4px;
78
+ }
79
+
80
+ .ctl-row-switch {
81
+ flex-direction: row;
82
+ align-items: center;
83
+ justify-content: space-between;
84
+ gap: 8px;
85
+ }
86
+
87
+ .ctl-label {
88
+ color: var(--color-muted-foreground);
89
+ font-size: 11px;
90
+ line-height: 1.4;
91
+ }
92
+
93
+ /* 分段按钮组:窄面板里自动换行,避免横向溢出 */
94
+ .ctl-seg {
95
+ display: flex;
96
+ flex-wrap: wrap;
97
+ gap: 4px;
98
+ }
99
+
100
+ .seg-btn {
101
+ flex: 0 1 auto;
102
+ padding: 3px 7px;
103
+ border: 1px solid var(--color-border);
104
+ border-radius: 5px;
105
+ background: var(--color-muted);
106
+ color: var(--color-foreground);
107
+ font-size: 11px;
108
+ white-space: nowrap;
109
+ }
110
+
111
+ /* ---- 选中态与按压反馈(选中的那一档用主色描边 + 主色文字 + 主色淡底)----
112
+ 为什么必须补:分段按钮/芯片组此前只有"可点"样式、没有"已选中"样式,
113
+ 用户点完看不出当前生效的是哪一档;而芯片(滤镜 12 档)连基础样式都没有,是裸按钮。 */
114
+ .ctl-chips {
115
+ display: flex;
116
+ flex-wrap: wrap;
117
+ gap: 4px;
118
+ }
119
+
120
+ /* ---- 面板内可选列表(动作列表 / 皮肤列表):同一套行样式 ----
121
+ 既有的原文:
122
+ 容器 className="space-y-1 max-h-48 overflow-y-auto pr-1"
123
+ 行 className="w-full text-left px-2 lg:px-3 py-1 lg:py-1.5 rounded-md text-sm transition-colors
124
+ flex items-center justify-between
125
+ 选中: bg-primary/15 text-primary border border-primary
126
+ 未选中: hover:bg-accent/60 border border-transparent"
127
+ 名称 className="truncate" 时长 className="text-[10px] text-muted-foreground font-mono shrink-0 ml-2"
128
+ 换算:max-h-48 = 192px;py-1/lg:py-1.5 = 4/6px;px-2/lg:px-3 = 8/12px;rounded-md = 6px;text-sm = 14px。 */
129
+ .ctl-list {
130
+ display: flex;
131
+ flex-direction: column;
132
+ gap: 4px;
133
+ max-height: 192px;
134
+ overflow-y: auto;
135
+ padding-right: 4px;
136
+ }
137
+
138
+ .ctl-list-row {
139
+ width: 100%;
140
+ display: flex;
141
+ align-items: center;
142
+ justify-content: space-between;
143
+ gap: 8px;
144
+ padding: 4px 8px;
145
+ border: 1px solid transparent; /* 未选中:透明描边,选中才显主色(避免文字跳动) */
146
+ border-radius: 6px;
147
+ background: transparent;
148
+ color: var(--color-foreground);
149
+ font-size: 14px;
150
+ text-align: left;
151
+ transition: background-color 200ms var(--ui-ease), color 200ms var(--ui-ease), border-color 200ms var(--ui-ease);
152
+ }
153
+
154
+ @media (min-width: 1024px) {
155
+ .ctl-list-row { padding: 6px 12px; }
156
+ }
157
+
158
+ .ctl-list-row:not(:disabled):hover {
159
+ background: var(--color-secondary);
160
+ }
161
+
162
+ .ctl-list-row[aria-pressed='true'] {
163
+ background: rgba(255, 255, 255, 0.06);
164
+ background: color-mix(in srgb, var(--color-primary) 15%, transparent);
165
+ border-color: var(--color-primary);
166
+ color: var(--color-primary);
167
+ }
168
+
169
+ .ctl-list-name {
170
+ overflow: hidden;
171
+ text-overflow: ellipsis;
172
+ white-space: nowrap;
173
+ min-width: 0;
174
+ }
175
+
176
+ .ctl-list-time {
177
+ flex: 0 0 auto;
178
+ margin-left: 8px;
179
+ font-size: 10px;
180
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
181
+ color: var(--color-muted-foreground);
182
+ }
183
+
184
+ .ctl-list-empty {
185
+ padding: 8px 0;
186
+ text-align: center;
187
+ font-size: 12px;
188
+ color: var(--color-muted-foreground);
189
+ }
190
+
191
+ /* ---- 资源根管理:列表要能滚(根多时不能只看到前几条)---- */
192
+ #root-manager-body {
193
+ max-height: min(38vh, 320px);
194
+ overflow-y: auto;
195
+ padding-right: 2px;
196
+ }
197
+
198
+ #root-list {
199
+ display: flex;
200
+ flex-direction: column;
201
+ }
202
+
203
+ /* ---- 目录浏览器(「选择目录」弹层,roots-ui.js 的结构)---- */
204
+ .dir-picker {
205
+ position: fixed;
206
+ inset: 0;
207
+ z-index: 60;
208
+ background: rgba(0, 0, 0, 0.55);
209
+ display: flex;
210
+ align-items: center;
211
+ justify-content: center;
212
+ }
213
+
214
+ .dir-picker-box {
215
+ width: min(560px, 92vw);
216
+ max-height: min(70vh, 640px);
217
+ display: flex;
218
+ flex-direction: column;
219
+ background: var(--color-card);
220
+ border: 1px solid var(--color-border);
221
+ border-radius: 10px;
222
+ box-shadow: 0 12px 40px rgba(0, 0, 0, 0.45);
223
+ overflow: hidden;
224
+ }
225
+
226
+ .dir-picker-head,
227
+ .dir-picker-foot {
228
+ display: flex;
229
+ align-items: center;
230
+ gap: 8px;
231
+ padding: 10px 14px;
232
+ border-bottom: 1px solid var(--color-border);
233
+ }
234
+
235
+ .dir-picker-foot {
236
+ border-bottom: none;
237
+ border-top: 1px solid var(--color-border);
238
+ flex-wrap: wrap;
239
+ font-size: 11px;
240
+ }
241
+
242
+ .dir-picker-foot input {
243
+ flex: 1 1 160px;
244
+ min-width: 0;
245
+ }
246
+
247
+ .dir-picker-spacer { flex: 1; }
248
+
249
+ .dir-picker-body {
250
+ padding: 12px 14px;
251
+ overflow: auto;
252
+ display: flex;
253
+ flex-direction: column;
254
+ gap: 8px;
255
+ }
256
+
257
+ .dir-picker-hint { font-size: 12px; color: var(--color-muted-foreground); }
258
+ .dir-picker-error { font-size: 12px; color: #e88; }
259
+ .dir-picker-list { display: flex; flex-direction: column; gap: 4px; }
260
+ .dir-picker-nav { width: 100%; text-align: left; }
261
+
262
+ /* ---- 左栏「选择目录」:面板上只有一个按钮 ---- */
263
+ .root-add {
264
+ display: flex;
265
+ flex-direction: column;
266
+ gap: 4px;
267
+ margin-bottom: 4px;
268
+ }
269
+
270
+ /* 「资源根 N 个」靠左,right 两侧按钮(收起 / 管理)挨着排在右端。
271
+ 用 margin-left:auto 把第一个按钮顶到右边,后续按钮自然紧随 —— 比 grid 三段式更简单,
272
+ 也不会因为两侧文字宽度变化而让按钮漂到中间。 */
273
+ .root-bar {
274
+ display: flex;
275
+ align-items: center;
276
+ gap: 6px;
277
+ }
278
+ .root-bar > #btn-collapse { margin-left: auto; }
279
+ .root-add > #btn-add-root {
280
+ align-self: flex-start;
281
+ }
282
+
283
+
284
+
285
+ .chip-btn {
286
+ flex: 0 1 auto;
287
+ padding: 3px 7px;
288
+ border: 1px solid var(--color-border);
289
+ border-radius: 999px;
290
+ background: var(--color-muted);
291
+ color: var(--color-foreground);
292
+ font-size: 11px;
293
+ white-space: nowrap;
294
+ cursor: pointer;
295
+ }
296
+
297
+ /* 悬停:主色描边,提示"可点" */
298
+ .seg-btn:not(:disabled):hover,
299
+ .chip-btn:not(:disabled):hover,
300
+ .ctl-actions .mini-btn:not(:disabled):hover {
301
+ border-color: var(--color-primary);
302
+ color: var(--color-primary);
303
+ }
304
+
305
+ /* 选中:主色描边 + 主色文字 + 主色淡底 + 加粗
306
+ (color-mix 前先给一层 rgba 兜底,老 Chromium 不支持 color-mix 时仍有可辨底色) */
307
+ .seg-btn[aria-pressed='true'],
308
+ .chip-btn[aria-pressed='true'] {
309
+ border-color: var(--color-primary);
310
+ color: var(--color-primary);
311
+ background: rgba(255, 255, 255, 0.06);
312
+ background: color-mix(in srgb, var(--color-primary) 16%, transparent);
313
+ font-weight: 600;
314
+ }
315
+
316
+ /* 按压:轻微下沉,给"点到了"的即时反馈 */
317
+ .seg-btn:not(:disabled):active,
318
+ .chip-btn:not(:disabled):active,
319
+ .ctl-actions .mini-btn:not(:disabled):active {
320
+ transform: translateY(1px);
321
+ }
322
+
323
+ /* 键盘可达:焦点环用主色,不依赖浏览器默认蓝框 */
324
+ .seg-btn:focus-visible,
325
+ .chip-btn:focus-visible,
326
+ .switch-btn:focus-visible,
327
+ .ctl-actions .mini-btn:focus-visible {
328
+ outline: 2px solid var(--color-primary);
329
+ outline-offset: 1px;
330
+ }
331
+
332
+ /* 未接线:视觉上明确"暂不可用",但仍然可读(不是隐藏) */
333
+ .seg-btn:disabled,
334
+ .switch-btn:disabled,
335
+ .transport-select:disabled,
336
+ #app-footer .mini-btn:disabled {
337
+ cursor: not-allowed;
338
+ opacity: 0.55;
339
+ }
340
+
341
+ /* 假开关:一个胶囊 + 圆点(M6 接线后由 aria-checked 驱动位置) */
342
+ .switch-btn {
343
+ flex: 0 0 auto;
344
+ width: 30px;
345
+ height: 16px;
346
+ padding: 2px;
347
+ border: 1px solid var(--color-border);
348
+ border-radius: 999px;
349
+ background: var(--color-secondary);
350
+ display: inline-flex;
351
+ align-items: center;
352
+ }
353
+
354
+ .switch-knob {
355
+ width: 10px;
356
+ height: 10px;
357
+ border-radius: 50%;
358
+ background: var(--color-muted-foreground);
359
+ transition: transform 0.15s ease;
360
+ }
361
+
362
+ .switch-btn[aria-checked='true'] {
363
+ background: var(--color-primary);
364
+ }
365
+
366
+ .switch-btn[aria-checked='true'] .switch-knob {
367
+ transform: translateX(14px);
368
+ background: #0a0a0c;
369
+ }
370
+
371
+ /* 动作按钮组(导出/导入/恢复默认) */
372
+ .ctl-actions {
373
+ display: flex;
374
+ flex-wrap: wrap;
375
+ gap: 4px;
376
+ }
377
+
378
+ /* 动态内容占位说明 */
379
+ .ctl-dynamic {
380
+ border: 1px dashed var(--color-border);
381
+ border-radius: 6px;
382
+ padding: 6px 8px;
383
+ }
384
+
385
+ .ctl-note {
386
+ margin: 0;
387
+ color: var(--color-muted-foreground);
388
+ font-size: 11px;
389
+ line-height: 1.5;
390
+ }
391
+
392
+ /* --------------------------------------------------- 底部播放条内部 */
393
+
394
+ /* 三段布局:左中右,组内按需换行不溢出 */
395
+ #app-footer .transport-group {
396
+ display: flex;
397
+ align-items: center;
398
+ gap: 6px;
399
+ min-width: 0;
400
+ }
401
+
402
+ #app-footer .mini-btn {
403
+ height: 26px;
404
+ padding: 0 8px;
405
+ font-size: 11px;
406
+ }
407
+
408
+ #app-footer .transport-select {
409
+ height: 26px;
410
+ max-width: 96px;
411
+ padding: 0 4px;
412
+ border: 1px solid var(--color-border);
413
+ border-radius: 6px;
414
+ background: var(--color-muted);
415
+ color: var(--color-foreground);
416
+ font-size: 11px;
417
+ }
418
+
419
+ /* 主题:dark-cyan(色值取自既有的 --color-* 变量,逐值一致) */
420
+ [data-ui-theme="dark-cyan"] {
421
+ --color-background: #131416;
422
+ --color-foreground: #e9ebed;
423
+ --color-card: #1f2326;
424
+ --color-primary: #1fc0c5;
425
+ --color-primary-foreground: #0a0a0c;
426
+ --color-secondary: #2a2f33;
427
+ --color-muted: #23272a;
428
+ --color-muted-foreground: #9ca3af;
429
+ --color-text-secondary: #9ca3af;
430
+ --color-border: #353a3e;
431
+ /* 内存三色条:底槽 / 可用 / 服务端已占用(深浅主题都保证三段可分辨) */
432
+ --mem-total: #2a2f33;
433
+ --mem-available: #3ddc84;
434
+ --mem-server: #e08b3a;
435
+ }
436
+
437
+ /* 主题:eye-gray(色值取自既有的 --color-* 变量,逐值一致) */
438
+ [data-ui-theme="eye-gray"] {
439
+ --color-background: #262624;
440
+ --color-foreground: #f0eee9;
441
+ --color-card: #30302e;
442
+ --color-primary: #d97757;
443
+ --color-primary-foreground: #1f1d1a;
444
+ --color-secondary: #3a3936;
445
+ --color-muted: #2e2d2b;
446
+ --color-muted-foreground: #b0aea5;
447
+ --color-text-secondary: #b0aea5;
448
+ --color-border: #4a4843;
449
+ /* 内存三色条:底槽 / 可用 / 服务端已占用(深浅主题都保证三段可分辨) */
450
+ --mem-total: #3a3a36;
451
+ --mem-available: #7fd68a;
452
+ --mem-server: #e09b5a;
453
+ }
454
+
455
+ /* 主题:day-paper(色值取自既有的 --color-* 变量,逐值一致) */
456
+ [data-ui-theme="day-paper"] {
457
+ --color-background: #f4f1ea;
458
+ --color-foreground: #262420;
459
+ --color-card: #fbf9f4;
460
+ --color-primary: #0e8a8f;
461
+ --color-primary-foreground: #ffffff;
462
+ --color-secondary: #e7e2d7;
463
+ --color-muted: #ece8de;
464
+ --color-muted-foreground: #5c574b;
465
+ --color-text-secondary: #5c574b;
466
+ --color-border: #d8d2c4;
467
+ /* 内存三色条:底槽 / 可用 / 服务端已占用(深浅主题都保证三段可分辨) */
468
+ --mem-total: #d8d3c8;
469
+ --mem-available: #2e8b57;
470
+ --mem-server: #c96a1e;
471
+ }
472
+
473
+ /* 主题:neon-violet(色值取自既有的 --color-* 变量,逐值一致) */
474
+ [data-ui-theme="neon-violet"] {
475
+ --color-background: #0b0814;
476
+ --color-foreground: #ece8f7;
477
+ --color-card: #16111f;
478
+ --color-primary: #a855f7;
479
+ --color-primary-foreground: #160a22;
480
+ --color-secondary: #221a30;
481
+ --color-muted: #1a1426;
482
+ --color-muted-foreground: #b0a6cc;
483
+ --color-text-secondary: #b0a6cc;
484
+ --color-border: #352b49;
485
+ /* 内存三色条:底槽 / 可用 / 服务端已占用(深浅主题都保证三段可分辨) */
486
+ --mem-total: #2d2140;
487
+ --mem-available: #4be3a2;
488
+ --mem-server: #ff9f45;
489
+ }
490
+
491
+ /* 主题:cloud-blue(色值取自既有的 --color-* 变量,逐值一致) */
492
+ [data-ui-theme="cloud-blue"] {
493
+ --color-background: #eef2f7;
494
+ --color-foreground: #16202e;
495
+ --color-card: #fbfcfe;
496
+ --color-primary: #2563eb;
497
+ --color-primary-foreground: #ffffff;
498
+ --color-secondary: #e2e8f0;
499
+ --color-muted: #e6ebf2;
500
+ --color-muted-foreground: #4f5e78;
501
+ --color-text-secondary: #4f5e78;
502
+ --color-border: #cbd5e1;
503
+ /* 内存三色条:底槽 / 可用 / 服务端已占用(深浅主题都保证三段可分辨) */
504
+ --mem-total: #2b3a4a;
505
+ --mem-available: #52d9a0;
506
+ --mem-server: #f0a04b;
507
+ }
508
+
509
+ /* 舞台底色:背景模式(本实现同时写存储与画面,切换即时生效)
510
+ 三种模式互斥:棋盘格画图案、纯色只上色、透明完全不上色;每条都显式清掉另一条的属性。 */
511
+ #stage { --stage-solid: #2a2a2a; }
512
+
513
+ /* 棋盘格:4 层 45° 渐变拼出 16×16 格;方块色 rgba(255,255,255,0.06),底色 rgb(23,26,28) */
514
+ #stage[data-bg="checkerboard"] {
515
+ background-color: rgb(23, 26, 28);
516
+ background-image:
517
+ linear-gradient(45deg, rgba(255,255,255,0.06) 25%, rgba(0,0,0,0) 25%),
518
+ linear-gradient(-45deg, rgba(255,255,255,0.06) 25%, rgba(0,0,0,0) 25%),
519
+ linear-gradient(45deg, rgba(0,0,0,0) 75%, rgba(255,255,255,0.06) 75%),
520
+ linear-gradient(-45deg, rgba(0,0,0,0) 75%, rgba(255,255,255,0.06) 75%);
521
+ background-size: 16px 16px, 16px 16px, 16px 16px, 16px 16px;
522
+ background-position: 0px 0px, 0px 8px, 8px -8px, -8px 0px;
523
+ background-repeat: repeat, repeat, repeat, repeat;
524
+ }
525
+
526
+ /* 纯色模式:沿用现有 --stage-solid 变量,清掉棋盘图案 */
527
+ #stage[data-bg="solid"] {
528
+ background-image: none;
529
+ background-color: var(--stage-solid, rgb(23, 26, 28));
530
+ }
531
+
532
+ /* 透明模式:完全不上色 */
533
+ #stage[data-bg="transparent"] {
534
+ background-image: none;
535
+ background-color: transparent;
536
+ }
537
+
538
+ /* ---- 删除线只表达「未接线」,不再表达「当前渲染器不支持」 ----
539
+ 为什么这么分(这不只是样式取舍,两种禁用是完全不同的事实):
540
+ · 未接线(data-pending):功能本应存在、但还没接上 → 划线,表示"这条先划掉";
541
+ · 能力不支持(data-disabled-reason):当前渲染器压根没有这项能力(如 Live2D 没有
542
+ 动画混合时长、部件筛除是 Spine 插槽概念)→ 只灰化 + 原生 title 说明,**不划线**。
543
+ 曾经两者共用 :disabled 划线,导致 Live2D 面「混合模式 / 皮肤列表 / 部件筛除」
544
+ 被划成"像坏了一样",掩盖了"这一面本来就没这个能力"的真实原因。
545
+ 颜色用弱化色,避免抢视觉。 */
546
+ #right-panel button[data-pending="true"],
547
+ #right-panel select[data-pending="true"],
548
+ #right-panel .chip-btn[data-pending="true"] {
549
+ text-decoration: line-through;
550
+ text-decoration-thickness: 1px;
551
+ text-decoration-color: var(--color-muted-foreground);
552
+ }
553
+
554
+ #right-panel .ctl-row:has(> [role="switch"][data-pending="true"]) > .ctl-label,
555
+ #right-panel .ctl-row:has(> select[data-pending="true"]) > .ctl-label,
556
+ #right-panel .ctl-row:has(> .ctl-chips > .chip-btn[data-pending="true"]) > .ctl-label {
557
+ text-decoration: line-through;
558
+ text-decoration-thickness: 1px;
559
+ text-decoration-color: var(--color-muted-foreground);
560
+ }
561
+
562
+ /* ---- 禁用 / 待接线控件的统一视觉:灰化 + 删除线 + 原因走原生 title ----
563
+ 覆盖三类控件(都归 panel.js 挂状态,本文件只负责呈现):
564
+ ① button:disabled —— 普通按钮、开关(<button role="switch">)、分段按钮
565
+ ② [role="switch"][aria-disabled="true"] —— 用 aria 表达禁用的开关
566
+ ③ .chip-btn[data-pending] —— 分段按钮组里标记待接线的单个按钮
567
+ 原因提示统一用 title 属性(浏览器原生 tooltip):表现层不复制文案、不写死原因,
568
+ 避免与 panel.js 的文案来源分叉;也不引入脚本或伪元素气泡。
569
+ 只作用于禁用态,可点控件不受影响。 */
570
+ #right-panel {
571
+ /* 禁用态强度集中在这里,便于整体调深浅(不散落在各处) */
572
+ --ctl-off-opacity: 0.5;
573
+ --ctl-off-saturate: 0.55;
574
+ }
575
+
576
+ /* 灰化 + 不可点光标:三类控件同一口径 */
577
+ #right-panel button:disabled,
578
+ #right-panel select:disabled,
579
+ #right-panel [role="switch"][aria-disabled="true"],
580
+ #right-panel .chip-btn[data-pending="true"] {
581
+ opacity: var(--ctl-off-opacity);
582
+ filter: saturate(var(--ctl-off-saturate));
583
+ cursor: not-allowed;
584
+ user-select: none;
585
+ }
586
+
587
+ /* 删除线:有可见文字的控件划自己的文字(分段按钮逐项精确);
588
+ 开关与下拉没有可见文字,则划掉所在行的 .ctl-label(见上一条规则组)。 */
589
+
590
+ /* ---- 底栏自绘下拉(替代原生 select)----
591
+ 结构与交互见 panel/transport.js 的 createPicker:触发按钮 + 弹出列表。
592
+ 视觉沿用底栏行内控件的底色/圆角/描边,选中项用主色。 */
593
+ .transport-picker {
594
+ position: relative;
595
+ display: inline-flex;
596
+ align-items: center;
597
+ }
598
+
599
+ .transport-picker-btn {
600
+ display: inline-flex;
601
+ align-items: center;
602
+ gap: 6px;
603
+ min-width: 96px;
604
+ max-width: 220px;
605
+ padding: 4px 8px;
606
+ border: 1px solid var(--color-border, rgba(255, 255, 255, 0.12));
607
+ border-radius: 6px;
608
+ background: var(--color-secondary, rgba(255, 255, 255, 0.06));
609
+ color: var(--color-foreground);
610
+ font-size: 13px;
611
+ cursor: pointer;
612
+ }
613
+
614
+ .transport-picker-btn:hover { border-color: var(--color-primary, #1fc0c5); }
615
+
616
+ .transport-picker-btn .picker-label {
617
+ flex: 1 1 auto;
618
+ overflow: hidden;
619
+ text-overflow: ellipsis;
620
+ white-space: nowrap;
621
+ text-align: left;
622
+ }
623
+
624
+ .transport-picker-btn::after {
625
+ content: '';
626
+ flex: 0 0 auto;
627
+ width: 6px;
628
+ height: 6px;
629
+ border-right: 1.5px solid currentColor;
630
+ border-bottom: 1.5px solid currentColor;
631
+ transform: rotate(45deg) translateY(-2px);
632
+ opacity: 0.7;
633
+ }
634
+
635
+ .transport-picker-list {
636
+ position: absolute;
637
+ bottom: calc(100% + 6px);
638
+ left: 0;
639
+ z-index: 40;
640
+ min-width: 100%;
641
+ max-width: 260px;
642
+ max-height: 260px;
643
+ overflow-y: auto;
644
+ margin: 0;
645
+ padding: 4px;
646
+ list-style: none;
647
+ border: 1px solid var(--color-border, rgba(255, 255, 255, 0.12));
648
+ border-radius: 8px;
649
+ background: var(--color-panel, #14181a);
650
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
651
+ }
652
+
653
+ .transport-picker-item {
654
+ padding: 5px 8px;
655
+ border-radius: 6px;
656
+ font-size: 13px;
657
+ color: var(--color-foreground);
658
+ cursor: pointer;
659
+ white-space: nowrap;
660
+ overflow: hidden;
661
+ text-overflow: ellipsis;
662
+
663
+ display: flex;
664
+ align-items: center;
665
+ }
666
+
667
+ .transport-picker-item:hover { background: var(--color-secondary, rgba(255, 255, 255, 0.06)); }
668
+ .transport-picker-item.is-active { outline: 1px solid var(--color-primary, #1fc0c5); }
669
+ .transport-picker-item[aria-selected='true'] { color: var(--color-primary, #1fc0c5); font-weight: 600; }
670
+
671
+ /* ---- 右栏「自定义图片」列表(主题背景组)----
672
+ 结构见 panel/background-list.js:状态行 + 上传按钮 + 若干行(缩略图 / 名称+大小 / 改名 / 删除)。 */
673
+ .ctl-dynamic[data-ctl='ctl-bg-list'] { display: flex; flex-direction: column; gap: 8px; }
674
+
675
+ .bg-actions { display: flex; gap: 6px; }
676
+
677
+ .bg-btn {
678
+ padding: 4px 8px;
679
+ border: 1px solid var(--color-border, rgba(255, 255, 255, 0.12));
680
+ border-radius: 6px;
681
+ background: var(--color-secondary, rgba(255, 255, 255, 0.06));
682
+ color: var(--color-foreground);
683
+ font-size: 12px;
684
+ cursor: pointer;
685
+ }
686
+
687
+ .bg-btn:hover { border-color: var(--color-primary, #1fc0c5); }
688
+ .bg-btn-primary { border-color: var(--color-primary, #1fc0c5); color: var(--color-primary, #1fc0c5); }
689
+ .bg-btn-danger.is-armed { border-color: #e05a5a; color: #e05a5a; font-weight: 600; }
690
+
691
+ .bg-hint { margin: 0; min-height: 0; font-size: 12px; color: var(--color-muted, #8b949e); }
692
+ .bg-hint[data-kind='error'] { color: #e05a5a; }
693
+
694
+ .bg-items { display: flex; flex-direction: column; gap: 6px; }
695
+
696
+ /* 行会换行:右栏被拖窄时(实测约 130px)缩略图与名称一行、动作按钮另起一行,
697
+ 否则名称会被压成一个字、按钮竖着叠起来看不清。 */
698
+ .bg-item {
699
+ display: flex;
700
+ flex-wrap: wrap;
701
+ align-items: center;
702
+ gap: 6px;
703
+ padding: 4px;
704
+ border: 1px solid transparent;
705
+ border-radius: 6px;
706
+ }
707
+
708
+ .bg-item:hover { border-color: var(--color-border, rgba(255, 255, 255, 0.12)); }
709
+
710
+ .bg-thumb {
711
+ flex: 0 0 auto;
712
+ width: 40px;
713
+ height: 40px;
714
+ padding: 0;
715
+ border: 1px solid var(--color-border, rgba(255, 255, 255, 0.12));
716
+ border-radius: 4px;
717
+ background: #000 center/cover no-repeat;
718
+ cursor: pointer;
719
+ overflow: hidden;
720
+ }
721
+
722
+ .bg-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
723
+ .bg-thumb:hover { border-color: var(--color-primary, #1fc0c5); }
724
+
725
+ .bg-meta { flex: 1 1 56px; min-width: 56px; display: flex; flex-direction: column; gap: 2px; }
726
+ .bg-item > .bg-btn { flex: 0 0 auto; }
727
+ .bg-name { font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
728
+ .bg-size { font-size: 11px; color: var(--color-muted, #8b949e); }
729
+
730
+ .bg-name-input {
731
+ width: 100%;
732
+ padding: 2px 4px;
733
+ border: 1px solid var(--color-primary, #1fc0c5);
734
+ border-radius: 4px;
735
+ background: var(--color-panel, #14181a);
736
+ color: var(--color-foreground);
737
+ font-size: 13px;
738
+ }
739
+
740
+ /* 编辑态:行内的「改名/删除」让位给「确定/取消」(窄面板下它们会重叠并拦住点击) */
741
+ .bg-item.is-editing > .bg-btn { display: none; }
742
+ .bg-item.is-editing .bg-meta { gap: 4px; }
743
+
744
+ /* 底栏背景下拉里的分组标题(内置模式 / 自定义图片的分界,不参与键盘高亮) */
745
+ .transport-picker-group {
746
+ padding: 6px 8px 3px;
747
+ margin-top: 2px;
748
+ border-top: 1px solid var(--color-border, rgba(255, 255, 255, 0.12));
749
+ font-size: 11px;
750
+ color: var(--color-muted, #8b949e);
751
+ cursor: default;
752
+ }
753
+
754
+ /* ---- 底栏三块对齐(配合 style.css 的 .app-footer grid minmax(0,1fr) auto minmax(0,1fr))----
755
+ 三块都必须 min-width:0:grid 列能收缩了,但块内是 flex,子项的隐式最小宽度同样会撑破。 */
756
+ #app-footer > .transport-group { min-width: 0; flex-wrap: nowrap; }
757
+ /* 按角色定列(不是 nth-child):空块被跳过时,剩下的块仍落在正确的列上 */
758
+ #app-footer > .transport-group[data-slot='left'] { grid-column: 1; justify-self: start; }
759
+ #app-footer > .transport-group[data-slot='center'] { grid-column: 2; justify-self: center; }
760
+ #app-footer > .transport-group[data-slot='right'] {
761
+ grid-column: 3;
762
+ justify-self: end;
763
+ justify-content: flex-end;
764
+ }
765
+
766
+ /* ---- 右栏 240px 下的自适应:控件行撑满、长文本省略、列表/分段不溢出 ---- */
767
+ #right-panel .ctl-row,
768
+ #right-panel .ctl-seg,
769
+ #right-panel .ctl-dynamic,
770
+ #right-panel .ctl-list { max-width: 100%; min-width: 0; }
771
+
772
+ #right-panel .ctl-seg { flex-wrap: wrap; } /* 四个档位在 240px 下可换行,不横向溢出 */
773
+ #right-panel .ctl-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
774
+ #right-panel .acc-trigger { width: 100%; min-width: 0; }
775
+
776
+ /* ---- 竖屏 / 窄屏:底栏只留图标(文字标签隐藏,按钮收成方形并保留触控尺寸)---- */
777
+ /* 纯图标断点:底栏"全部显示不裁字"时内容宽约 1068px(7 个控件 + 名称),
778
+ 加上左右各 16px 边距 ⇒ 视口 < 1100px 就放不下,此时自动转纯图标模式
779
+ (否则只能靠裁字/换行/压盖,三者都不合需求)。竖屏同样命中这条。 */
780
+ @media (orientation: portrait), (max-width: 1100px) {
781
+ /* 只显示图标后右块仍可能放不下一行(4 个 32px 按钮 + 间距 > 列宽)⇒ 允许换行,
782
+ 此时浮条必须能长高,否则换行出来的按钮会溢出浮条底边。 */
783
+ #app-footer { height: auto; min-height: var(--footer-h); padding: 6px 9px; }
784
+
785
+ #app-footer button .btn-label,
786
+ #app-footer .transport-picker-btn .picker-label { display: none; }
787
+
788
+ #app-footer .transport-picker-btn {
789
+ min-width: 32px;
790
+ /* 触控目标不小于 32×32:只留图标时按钮变方,高度也要补够 */
791
+ min-height: 32px;
792
+ /* 纯图标时左右 padding 会把图标推偏 ⇒ 清零并居中 */
793
+ padding: 0;
794
+ justify-content: center;
795
+ }
796
+
797
+ /* 只留图标时去掉下拉箭头(否则一个方块里挤两个图形) */
798
+ #app-footer .transport-picker-btn::after { display: none; }
799
+
800
+ #app-footer .mini-btn {
801
+ min-width: 32px;
802
+ min-height: 32px;
803
+ padding: 0;
804
+ justify-content: center;
805
+ }
806
+
807
+ /* 图标本身不能有左右外边距残留(否则居中会偏) */
808
+ #app-footer .btn-icon { margin: 0; }
809
+
810
+ #app-footer .transport-picker-list { min-width: 140px; }
811
+ }
812
+
813
+ /* 底栏的下拉按内容自然宽度显示(浮条宽度是 fit-content,不靠裁字来省空间)。
814
+ 只解除右栏那套 220px 上限,名称不省略。 */
815
+ #app-footer .transport-picker-btn { max-width: none; }
816
+ #app-footer .transport-picker-btn .picker-label {
817
+ max-width: none;
818
+ overflow: visible;
819
+ text-overflow: clip;
820
+ }
821
+
822
+ /* ---- PMA 预乘:档位 2×2 + 四行只读诊断(布局与信息量)----
823
+ 布局:2×2 四个档位 + 下方一组 11px 小字诊断行。
824
+ 四行的意义:让人(和 AI)一眼看出"这个模型到底按哪种预乘在渲染"——
825
+ Atlas 声明 / 像素检测 是输入证据,直通支持 是运行时能力,实际生效 是最终结论。 */
826
+ #right-panel .ctl-seg[data-layout='grid-2'] {
827
+ display: grid;
828
+ grid-template-columns: 1fr 1fr;
829
+ gap: 4px;
830
+ }
831
+
832
+ #right-panel .ctl-seg[data-layout='grid-2'] .seg-btn {
833
+ /* 比默认 chip 大一档(高 32px / 圆角 6px / 字号 12px / 左右内边距 12px),才像按钮 */
834
+ justify-content: center;
835
+ min-width: 0;
836
+ min-height: 32px;
837
+ padding: 0 12px;
838
+ border-radius: 6px;
839
+ font-size: 12px;
840
+ }
841
+
842
+ /* 四行小字:只读、不可点(纯诊断,不抢控件的视觉层级) */
843
+ #right-panel .ctl-diag {
844
+ display: flex;
845
+ flex-direction: column;
846
+ gap: 2px;
847
+ margin-top: 6px;
848
+ font-size: 11px;
849
+ line-height: 1.45;
850
+ /* 用专门的诊断文字令牌:每个主题块都有定义,切主题自动跟随(不写字面色值)。
851
+ 之前误用 --color-muted —— 那是"深色底"的名字,当文字色就是黑字压深底、标签看不见。 */
852
+ color: var(--color-text-secondary);
853
+ }
854
+
855
+ #right-panel .ctl-diag-row {
856
+ display: flex;
857
+ gap: 4px;
858
+ min-width: 0;
859
+ }
860
+
861
+ #right-panel .ctl-diag-value {
862
+ overflow: hidden;
863
+ color: var(--color-foreground);
864
+ text-overflow: ellipsis;
865
+ white-space: nowrap;
866
+ }
867
+
868
+ /* 实际生效 = 最终结论:主色 + 加重 */
869
+ #right-panel .ctl-diag-value.is-primary {
870
+ font-weight: 500;
871
+ color: var(--color-primary);
872
+ }
873
+
874
+ /* ---------------- 滑块控件(播放速度 / 混合时长) ----------------
875
+ 结构:标签在左、当前值在右(等宽),滑块单独一行占满宽度 —— 。
876
+ 契约《禁止内联 CSS 与 JS》:颜色/尺寸/字体全部走 CSS 变量,JS 不写任何 element.style;
877
+ 轨道与手柄用原生 `accent-color` 跟随主题主色,无需自绘。 */
878
+ .ctl-slider {
879
+ display: flex;
880
+ flex-direction: column;
881
+ gap: 4px; /* 标签行与滑块的间距 */
882
+ padding: 2px 0;
883
+ }
884
+
885
+ .ctl-slider-head {
886
+ display: flex;
887
+ align-items: center;
888
+ justify-content: space-between; /* 标签左、值右 */
889
+ gap: 8px;
890
+ font-size: 12px;
891
+ line-height: 1.4;
892
+ }
893
+
894
+ .ctl-slider-label {
895
+ color: var(--color-muted-foreground);
896
+ }
897
+
898
+ /* 当前值:等宽字体 + 表格数字,避免拖动时宽度跳动 */
899
+ .ctl-slider-value {
900
+ font-family: var(--font-mono, ui-monospace, monospace);
901
+ font-variant-numeric: tabular-nums;
902
+ color: var(--color-foreground);
903
+ cursor: pointer; /* 双击复位默认值 */
904
+ user-select: none;
905
+ }
906
+
907
+ /* 滑块的定位容器:刻度线用伪元素叠在轨道中线(原生 input 内部结构不可插入伪元素) */
908
+ .ctl-slider-track {
909
+ position: relative;
910
+ display: flex;
911
+ align-items: center;
912
+ width: 100%;
913
+ }
914
+
915
+ /* 基准刻度:中线(速度的 1.0x 正好落在正中)
916
+ 注意:accent-color 不作用于伪元素,所以颜色必须自己指定主题令牌 */
917
+ .ctl-slider-track::after {
918
+ content: '';
919
+ position: absolute;
920
+ left: 50%;
921
+ top: 50%;
922
+ transform: translate(-50%, -50%);
923
+ width: 2px;
924
+ height: 10px;
925
+ border-radius: 1px;
926
+ background: var(--color-muted-foreground);
927
+ opacity: 0.45;
928
+ pointer-events: none; /* 不挡拖动 */
929
+ }
930
+
931
+ .ctl-slider-input {
932
+ width: 100%;
933
+ margin: 0;
934
+ cursor: pointer;
935
+ accent-color: var(--color-primary); /* 轨道/手柄颜色跟随五套主题 */
936
+ }
937
+
938
+ /* 键盘可达性:焦点用主色描边(不写字面色值) */
939
+ .ctl-slider-input:focus-visible {
940
+ outline: 2px solid var(--color-primary);
941
+ outline-offset: 2px;
942
+ border-radius: 999px;
943
+ }
944
+
945
+ /* 禁用态(当前渲染面未声明该能力时由面板压制) */
946
+ .ctl-slider-input:disabled {
947
+ opacity: var(--ctl-off-opacity);
948
+ cursor: not-allowed;
949
+ }
950
+
951
+
952
+ /* 隐藏的文件选择器(导入/导出、自定义背景用)
953
+ 为什么不写内联 style:契约《禁止内联CSS与JS》——定位交给类名,JS 只加类。
954
+ 用 position:fixed + 视口外偏移而非 display:none:某些浏览器对 display:none 的 input 不弹选择框。 */
955
+ .file-picker-hidden {
956
+ position: fixed;
957
+ left: -9999px;
958
+ top: 0;
959
+ width: 1px;
960
+ height: 1px;
961
+ opacity: 0;
962
+ pointer-events: none;
963
+ }
964
+
965
+
966
+ /* ---- 纯色预设列表(ctl-bg-solids)----
967
+ 与自定义图片列表同构:行 = 色块 + 名称 + 操作;底部 = 添加 / 恢复默认。
968
+ 色块颜色是运行时数据 ⇒ 由 --swatch 变量驱动,不在 JS 里拼 background 字面量。 */
969
+ .bg-solids { display: flex; flex-direction: column; gap: 6px; }
970
+
971
+ .bg-solid-row {
972
+ display: flex;
973
+ align-items: center;
974
+ gap: 6px;
975
+ padding: 4px 6px;
976
+ border: 1px solid transparent;
977
+ border-radius: 6px;
978
+ }
979
+
980
+ .bg-solid-row:hover { border-color: var(--color-border, rgba(255, 255, 255, 0.12)); }
981
+
982
+ .bg-solid-swatch {
983
+ flex: 0 0 auto;
984
+ width: 20px;
985
+ height: 20px;
986
+ padding: 0;
987
+ border-radius: 4px;
988
+ border: 1px solid rgba(255, 255, 255, 0.22);
989
+ background: var(--swatch, #2a2a2a);
990
+ cursor: pointer;
991
+ }
992
+
993
+ .bg-solid-swatch:hover { border-color: var(--color-primary, #1fc0c5); }
994
+
995
+ .bg-solid-name {
996
+ flex: 1 1 auto;
997
+ min-width: 0;
998
+ font-size: 13px;
999
+ overflow: hidden;
1000
+ text-overflow: ellipsis;
1001
+ white-space: nowrap;
1002
+ }
1003
+
1004
+ .bg-solid-foot { display: flex; gap: 6px; }
1005
+
1006
+ .bg-solid-hint { margin: 0; min-height: 0; font-size: 12px; color: var(--color-muted, #8b949e); }
1007
+ .bg-solid-hint[data-kind='error'] { color: #e05a5a; }
1008
+
1009
+
1010
+ /* ---- 背景来源统一列表(bg-source.renderSourceGroup)----
1011
+ 选中态用「描边 + ✓」双标识:只靠颜色变化在灰度/色盲下无法分辨。 */
1012
+ .bg-src-row {
1013
+ display: flex;
1014
+ align-items: center;
1015
+ gap: 6px;
1016
+ padding: 4px 6px;
1017
+ border: 1px solid transparent;
1018
+ border-radius: 6px;
1019
+ cursor: pointer;
1020
+ }
1021
+
1022
+ .bg-src-row:hover { border-color: var(--color-border, rgba(255, 255, 255, 0.12)); }
1023
+
1024
+ /* 选中:描边 + 勾(两处都给,缺一不可) */
1025
+ .bg-src-row[data-selected='1'] {
1026
+ border-color: var(--color-primary, #1fc0c5);
1027
+ background: color-mix(in srgb, var(--color-primary, #1fc0c5) 12%, transparent);
1028
+ }
1029
+
1030
+ .bg-src-tick {
1031
+ flex: 0 0 12px;
1032
+ width: 12px;
1033
+ text-align: center;
1034
+ font-size: 12px;
1035
+ color: var(--color-primary, #1fc0c5);
1036
+ }
1037
+
1038
+ .bg-src-swatch {
1039
+ flex: 0 0 auto;
1040
+ width: 40px;
1041
+ height: 32px;
1042
+ border-radius: 4px;
1043
+ border: 1px solid rgba(255, 255, 255, 0.22);
1044
+ background: var(--swatch, #2a2a2a);
1045
+ }
1046
+
1047
+ .bg-src-thumb {
1048
+ flex: 0 0 auto;
1049
+ /* 56×32:再小就分不清是哪张图(用户明确要求放大);object-fit: cover 由 background-size 等价实现 */
1050
+ width: 56px;
1051
+ height: 32px;
1052
+ border-radius: 4px;
1053
+ border: 1px solid rgba(255, 255, 255, 0.22);
1054
+ background-color: rgba(255, 255, 255, 0.06);
1055
+ background-image: var(--thumb, none);
1056
+ background-size: cover;
1057
+ background-position: center;
1058
+ }
1059
+
1060
+ .bg-src-name {
1061
+ flex: 1 1 auto;
1062
+ min-width: 0;
1063
+ font-size: 13px;
1064
+ overflow: hidden;
1065
+ text-overflow: ellipsis;
1066
+ white-space: nowrap;
1067
+ }
1068
+
1069
+ .bg-src-foot { display: flex; gap: 6px; margin-top: 4px; }
1070
+
1071
+ /* 底栏菜单里的行更紧凑(没有行内操作按钮) */
1072
+ .bg-src-row--menu { padding: 3px 6px; }
1073
+
1074
+
1075
+ /* ---- 底栏 picker:二级菜单 + 按钮上的当前项预览 ----
1076
+ 二级是"侧向弹出":相对 picker 根定位到菜单右侧、底对齐(底栏在屏幕底部,向上长才对)。 */
1077
+ .transport-picker-sub {
1078
+ position: absolute;
1079
+ left: 100%;
1080
+ bottom: 0;
1081
+ margin-left: 4px;
1082
+ min-width: 168px;
1083
+ max-height: 46vh;
1084
+ overflow-y: auto;
1085
+ padding: 4px;
1086
+ z-index: 60;
1087
+ border-radius: 8px;
1088
+ border: 1px solid var(--color-border, #3a3f45);
1089
+ background: var(--color-card, #23272b);
1090
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
1091
+ }
1092
+
1093
+ /* 一级里"当前展开二级"的那一项给个标记,避免用户不知道二级是从哪来的 */
1094
+ .transport-picker-item.is-sub {
1095
+ border-left: 2px solid var(--color-primary, #1fc0c5);
1096
+ }
1097
+
1098
+ .transport-picker-arrow {
1099
+ margin-left: auto;
1100
+ padding-left: 6px;
1101
+ opacity: 0.7;
1102
+ font-size: 11px;
1103
+ }
1104
+
1105
+ /* 按钮上的当前项预览:纯色给色块、自定义给缩略图(颜色/图地址由 CSS 变量下发) */
1106
+ .picker-prev {
1107
+ flex: 0 0 auto;
1108
+ width: 14px;
1109
+ height: 14px;
1110
+ margin-right: 4px;
1111
+ border-radius: 3px;
1112
+ border: 1px solid rgba(255, 255, 255, 0.25);
1113
+ background-color: var(--prev-swatch, transparent);
1114
+ background-image: var(--prev-thumb, none);
1115
+ background-size: cover;
1116
+ background-position: center;
1117
+ }
1118
+
1119
+
1120
+ /* ---- 背景来源行:单行 + 右侧 ⋯ 菜单 ----
1121
+ 行高目标:纯色 ≈38px(色块 32 + 上下 3)、自定义 ≈38px(缩略图 32 + 上下 3)。
1122
+ 为什么单行:手机上两行式 5 个颜色就是 320px,列表一长必须滚。 */
1123
+ .bg-src-row {
1124
+ min-height: 38px;
1125
+ padding: 3px 4px;
1126
+ flex-wrap: nowrap;
1127
+ }
1128
+
1129
+ /* ⋯ 按钮:点击区 ≥32×32(手指要能点准),视觉上仍是小字 */
1130
+ .bg-src-more {
1131
+ flex: 0 0 auto;
1132
+ width: 32px;
1133
+ height: 32px;
1134
+ display: inline-flex;
1135
+ align-items: center;
1136
+ justify-content: center;
1137
+ padding: 0;
1138
+ border: 0;
1139
+ border-radius: 6px;
1140
+ background: transparent;
1141
+ color: var(--color-muted, #8b949e);
1142
+ font-size: 16px;
1143
+ line-height: 1;
1144
+ cursor: pointer;
1145
+ }
1146
+
1147
+ .bg-src-more:hover {
1148
+ background: rgba(255, 255, 255, 0.08);
1149
+ color: var(--color-fg, #e6e8ea);
1150
+ }
1151
+
1152
+ /* 行菜单:贴 ⋯ 弹出的浮动小面板(fixed + JS 算坐标) */
1153
+ .bg-src-menu {
1154
+ position: fixed;
1155
+ z-index: 9500;
1156
+ min-width: 112px;
1157
+ padding: 4px;
1158
+ border-radius: 8px;
1159
+ border: 1px solid var(--color-border, #3a3f45);
1160
+ background: var(--color-card, #23272b);
1161
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
1162
+ }
1163
+
1164
+ .bg-src-menu-item {
1165
+ display: block;
1166
+ width: 100%;
1167
+ /* 行高 ≥36px:触屏可点 */
1168
+ min-height: 36px;
1169
+ padding: 0 10px;
1170
+ border: 0;
1171
+ border-radius: 6px;
1172
+ background: transparent;
1173
+ color: var(--color-fg, #e6e8ea);
1174
+ font-size: 13px;
1175
+ text-align: left;
1176
+ cursor: pointer;
1177
+ }
1178
+
1179
+ .bg-src-menu-item:hover { background: rgba(255, 255, 255, 0.08); }
1180
+ .bg-src-menu-item--danger { color: #e05a5a; }
1181
+
1182
+
1183
+ /* ---- 内存三色条(panel/memory-bar.js)----
1184
+ 轨道 = 总内存;三段宽度 = 值 / 总 × 100%(由 JS 以 --seg-w 下发,样式表只用 var)。
1185
+ 颜色一律走 --mem-* 令牌(每套主题各一份),不写内联色值。 */
1186
+ .mem-bar {
1187
+ display: flex;
1188
+ flex-direction: column;
1189
+ gap: 4px;
1190
+ margin-top: 6px;
1191
+ }
1192
+
1193
+ .mem-bar-track {
1194
+ display: flex;
1195
+ width: 100%;
1196
+ height: 10px;
1197
+ border-radius: 5px;
1198
+ overflow: hidden;
1199
+ background: var(--mem-total);
1200
+ }
1201
+
1202
+ .mem-bar-seg {
1203
+ flex: 0 0 auto;
1204
+ width: var(--seg-w, 0%);
1205
+ height: 100%;
1206
+ }
1207
+
1208
+ .mem-bar-seg--server { background: var(--mem-server); }
1209
+ .mem-bar-seg--other { background: var(--mem-total); }
1210
+ .mem-bar-seg--avail { background: var(--mem-available); }
1211
+
1212
+ .mem-bar-text {
1213
+ margin: 0;
1214
+ font-size: 11px;
1215
+ line-height: 1.4;
1216
+ color: var(--color-muted-foreground);
1217
+ /* 等宽数字:数值变化时文字不会左右抖 */
1218
+ font-variant-numeric: tabular-nums;
1219
+ }
1220
+
1221
+ /* 顶栏内的内存条宿主:吃掉 brand 与右侧操作之间的弹性空间,垂直居中。
1222
+ 为什么不再绝对定位:浮在舞台上会遮挡画面,而且顶栏进(影院模式滑出)时它留在原地。
1223
+ pointer-events:none:本条不可点,不能挡住顶栏按钮的点击热区。 */
1224
+ .mem-bar-host--header {
1225
+ flex: 1 1 auto;
1226
+ min-width: 0;
1227
+ display: flex;
1228
+ align-items: center;
1229
+ justify-content: center;
1230
+ overflow: hidden;
1231
+ pointer-events: none;
1232
+ }
1233
+
1234
+ /* 顶栏里尺寸收一档:48px 高的顶栏要放下「6px 条 + 一行文字」 */
1235
+ .mem-bar-host--header .mem-bar {
1236
+ margin-top: 0;
1237
+ gap: 3px;
1238
+ width: 100%;
1239
+ max-width: 420px;
1240
+ }
1241
+
1242
+ .mem-bar-host--header .mem-bar-track {
1243
+ height: 6px;
1244
+ border-radius: 3px;
1245
+ /* 顶栏背景(--color-card)与底槽色(--mem-total)明度太近,"其他"段会与背景融在一起 ——
1246
+ 实拍确认只看得见绿色那段。给轨道加一圈描边把全长圈出来,不改任何颜色令牌。 */
1247
+ border: 1px solid var(--color-border);
1248
+ box-sizing: content-box;
1249
+ }
1250
+
1251
+ .mem-bar-host--header .mem-bar-text {
1252
+ font-size: 11px;
1253
+ line-height: 1.2;
1254
+ text-align: center;
1255
+ white-space: nowrap;
1256
+ }
1257
+
1258
+ /* 窄屏(触摸端为主):字号与条再收一档,尽量让三段数值都挤进顶栏 */
1259
+ @media (max-width: 720px) {
1260
+ .mem-bar-host--header .mem-bar-text { font-size: 10px; }
1261
+ .mem-bar-host--header .mem-bar-track { height: 5px; }
1262
+ }
1263
+
1264
+ @media (max-width: 480px) {
1265
+ .mem-bar-host--header .mem-bar-text { font-size: 9px; }
1266
+ .mem-bar-host--header .mem-bar { max-width: none; }
1267
+ }
1268
+
1269
+ /* 设置面板里那一条:撑满面板宽度,允许点选其它控件(本条不可点,别挡路) */
1270
+ .mem-bar-host--panel {
1271
+ margin-top: 2px;
1272
+ }
1273
+
1274
+
1275
+ /* ---- 目录选择对话框:失败原因行 ----
1276
+ 为什么放在对话框内部:它弹在屏幕中央,而资源根列表的提示在左栏(用户背后)⇒ 等于没有提示。
1277
+ 失败时对话框不关,用户在原地改完就能重试。 */
1278
+ .dir-picker-alert {
1279
+ margin: 0 14px 10px;
1280
+ padding: 8px 10px;
1281
+ border: 1px solid var(--color-danger);
1282
+ border-radius: 6px;
1283
+ background: color-mix(in srgb, var(--color-danger) 12%, transparent);
1284
+ color: var(--color-danger);
1285
+ font-size: 12px;
1286
+ line-height: 1.5;
1287
+ /* 长错误(带路径/原因)要能换行,别把对话框撑破 */
1288
+ word-break: break-word;
1289
+ }
1290
+
1291
+ /* 目录选择对话框的「添加」按钮不再单独定义样式 —— 已收敛到
1292
+ components.css 的 .mini-btn.is-accent(与左栏「选择目录」共用同一个类)。 */
1293
+
1294
+ /* 底部输入框与两个按钮等高对齐(原来输入框比按钮高一截,视觉参差) */
1295
+ .dir-picker-foot input,
1296
+ .dir-picker-foot .mini-btn {
1297
+ height: 28px;
1298
+ box-sizing: border-box;
1299
+ }
1300
+
1301
+ .dir-picker-foot .mini-btn {
1302
+ display: inline-flex;
1303
+ align-items: center;
1304
+ }