cortico 0.1.0

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 (311) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +47 -0
  3. package/package.json +43 -0
  4. package/src/boot.ts +46 -0
  5. package/src/bot.ts +1342 -0
  6. package/src/config-file.ts +41 -0
  7. package/src/core/README.md +92 -0
  8. package/src/core/billing.ts +26 -0
  9. package/src/core/blobs.ts +106 -0
  10. package/src/core/bus.ts +311 -0
  11. package/src/core/config-schema.ts +201 -0
  12. package/src/core/config.ts +262 -0
  13. package/src/core/core.ts +699 -0
  14. package/src/core/cost.ts +244 -0
  15. package/src/core/event-store.ts +304 -0
  16. package/src/core/fork.ts +113 -0
  17. package/src/core/generation.ts +118 -0
  18. package/src/core/instance-lock.ts +215 -0
  19. package/src/core/ipc-logger.ts +105 -0
  20. package/src/core/language.ts +62 -0
  21. package/src/core/log-context.ts +38 -0
  22. package/src/core/loop.ts +1858 -0
  23. package/src/core/markers.ts +48 -0
  24. package/src/core/prefix.ts +121 -0
  25. package/src/core/run.ts +95 -0
  26. package/src/core/secrets.ts +18 -0
  27. package/src/core/session.ts +56 -0
  28. package/src/core/sessions.ts +261 -0
  29. package/src/core/state.ts +104 -0
  30. package/src/core/template.ts +72 -0
  31. package/src/core/timers.ts +129 -0
  32. package/src/core/tool-log.ts +135 -0
  33. package/src/core/transcript.ts +92 -0
  34. package/src/core/truncate.ts +121 -0
  35. package/src/core/types.ts +1140 -0
  36. package/src/core/usage-log.ts +79 -0
  37. package/src/core/util.ts +386 -0
  38. package/src/deploy-listing.ts +160 -0
  39. package/src/deploy.ts +162 -0
  40. package/src/extensions/README.md +72 -0
  41. package/src/extensions/dry-mount.ts +390 -0
  42. package/src/extensions/manifest.ts +153 -0
  43. package/src/extensions/runtime.ts +42 -0
  44. package/src/extensions.ts +519 -0
  45. package/src/launcher.ts +285 -0
  46. package/src/paths.ts +155 -0
  47. package/src/protocol/open-responses/LICENSE +201 -0
  48. package/src/protocol/open-responses/README.md +14 -0
  49. package/src/protocol/open-responses/context-helpers.ts +27 -0
  50. package/src/protocol/open-responses/context-log.ts +59 -0
  51. package/src/protocol/open-responses/context.ts +89 -0
  52. package/src/protocol/open-responses/generated.ts +115 -0
  53. package/src/protocol/open-responses/index.ts +33 -0
  54. package/src/protocol/open-responses/openapi.json +4230 -0
  55. package/src/protocol/open-responses/stream.ts +146 -0
  56. package/src/protocol/open-responses/tokens.ts +31 -0
  57. package/src/providers/README.md +86 -0
  58. package/src/providers/base.ts +111 -0
  59. package/src/providers/configuration.ts +97 -0
  60. package/src/providers/console/config.ts +21 -0
  61. package/src/providers/console/settings.ts +443 -0
  62. package/src/providers/console/strings.ts +74 -0
  63. package/src/providers/console/types.ts +11 -0
  64. package/src/providers/llamacpp/archive.ts +148 -0
  65. package/src/providers/llamacpp/catalog.ts +247 -0
  66. package/src/providers/llamacpp/config.ts +36 -0
  67. package/src/providers/llamacpp/console/client.ts +6 -0
  68. package/src/providers/llamacpp/console/models-panel.ts +120 -0
  69. package/src/providers/llamacpp/console/runtime-panel.ts +173 -0
  70. package/src/providers/llamacpp/console/server.ts +153 -0
  71. package/src/providers/llamacpp/index.ts +95 -0
  72. package/src/providers/llamacpp/native.ts +51 -0
  73. package/src/providers/llamacpp/options.ts +117 -0
  74. package/src/providers/llamacpp/runtime-store.ts +139 -0
  75. package/src/providers/llamacpp/runtime.ts +214 -0
  76. package/src/providers/llamacpp/server.ts +286 -0
  77. package/src/providers/llamacpp/strings.ts +230 -0
  78. package/src/providers/openai-responses-compat/config.ts +20 -0
  79. package/src/providers/openai-responses-compat/index.ts +89 -0
  80. package/src/providers/openai-responses-compat/native.ts +127 -0
  81. package/src/providers/openai-responses-compat/strings.ts +19 -0
  82. package/src/providers/pricebook.ts +75 -0
  83. package/src/providers/registry.ts +137 -0
  84. package/src/providers/strings.ts +71 -0
  85. package/src/providers/transport/chat.ts +156 -0
  86. package/src/providers/transport/errors.ts +48 -0
  87. package/src/providers/transport/history.ts +68 -0
  88. package/src/providers/transport/native-input.ts +83 -0
  89. package/src/providers/transport/native-types.ts +13 -0
  90. package/src/providers/transport/response-assembly.ts +191 -0
  91. package/src/providers/transport/response-http.ts +196 -0
  92. package/src/providers/transport/response-meters.ts +27 -0
  93. package/src/providers/transport/responses-input.ts +56 -0
  94. package/src/web/README.md +84 -0
  95. package/src/web/client/console-pages/builtins/llm-settings/panel.ts +625 -0
  96. package/src/web/client/console-pages/builtins/llm-settings/pricing-panel.ts +168 -0
  97. package/src/web/client/console-pages/builtins/llm-settings/strings.ts +213 -0
  98. package/src/web/client/console-pages/builtins.ts +10 -0
  99. package/src/web/client/console-pages/context.ts +135 -0
  100. package/src/web/client/console-pages/host.ts +633 -0
  101. package/src/web/client/console-pages/loader.ts +132 -0
  102. package/src/web/client/console-pages/strings.ts +90 -0
  103. package/src/web/client/console-pages/tools/strings.ts +49 -0
  104. package/src/web/client/console-pages/tools/view.ts +215 -0
  105. package/src/web/client/core/api.ts +200 -0
  106. package/src/web/client/core/language.ts +56 -0
  107. package/src/web/client/core/lifecycle.ts +132 -0
  108. package/src/web/client/core/router.ts +199 -0
  109. package/src/web/client/core/stream.ts +157 -0
  110. package/src/web/client/core/websocket.ts +82 -0
  111. package/src/web/client/features/appearance/index.ts +333 -0
  112. package/src/web/client/features/appearance/strings.ts +87 -0
  113. package/src/web/client/features/config/index.ts +11 -0
  114. package/src/web/client/features/config/strings.ts +41 -0
  115. package/src/web/client/features/config/view.ts +428 -0
  116. package/src/web/client/features/core/events.ts +197 -0
  117. package/src/web/client/features/core/index.ts +254 -0
  118. package/src/web/client/features/core/run.ts +56 -0
  119. package/src/web/client/features/core/runlog.ts +214 -0
  120. package/src/web/client/features/core/sessions.ts +53 -0
  121. package/src/web/client/features/core/strings.ts +157 -0
  122. package/src/web/client/features/extensions/index.ts +422 -0
  123. package/src/web/client/features/extensions/strings.ts +164 -0
  124. package/src/web/client/features/feature.ts +93 -0
  125. package/src/web/client/features/live/context.ts +280 -0
  126. package/src/web/client/features/live/fork.ts +156 -0
  127. package/src/web/client/features/live/index.ts +392 -0
  128. package/src/web/client/features/live/onboarding.ts +113 -0
  129. package/src/web/client/features/live/protocol.ts +122 -0
  130. package/src/web/client/features/live/sessions.ts +71 -0
  131. package/src/web/client/features/live/status.ts +68 -0
  132. package/src/web/client/features/live/strings.ts +252 -0
  133. package/src/web/client/features/live/timeline.ts +590 -0
  134. package/src/web/client/features/prompts/editor.ts +292 -0
  135. package/src/web/client/features/prompts/index.ts +346 -0
  136. package/src/web/client/features/prompts/strings.ts +136 -0
  137. package/src/web/client/features/prompts/view.ts +256 -0
  138. package/src/web/client/features/providers/index.ts +100 -0
  139. package/src/web/client/features/providers/strings.ts +23 -0
  140. package/src/web/client/features/settings/general.ts +28 -0
  141. package/src/web/client/features/settings/index.ts +82 -0
  142. package/src/web/client/features/settings/strings.ts +33 -0
  143. package/src/web/client/features/storage/index.ts +1 -0
  144. package/src/web/client/features/storage/strings.ts +45 -0
  145. package/src/web/client/features/storage/view.ts +154 -0
  146. package/src/web/client/features/usage/chart.ts +486 -0
  147. package/src/web/client/features/usage/index.ts +407 -0
  148. package/src/web/client/features/usage/labels.ts +46 -0
  149. package/src/web/client/features/usage/range.ts +36 -0
  150. package/src/web/client/features/usage/state.ts +37 -0
  151. package/src/web/client/features/usage/strings.ts +191 -0
  152. package/src/web/client/features/usage/tooltip.ts +86 -0
  153. package/src/web/client/features/usage/types.ts +96 -0
  154. package/src/web/client/features/worlds/index.ts +424 -0
  155. package/src/web/client/features/worlds/strings.ts +121 -0
  156. package/src/web/client/main.ts +273 -0
  157. package/src/web/client/shell/avatar.ts +233 -0
  158. package/src/web/client/shell/index.ts +549 -0
  159. package/src/web/client/shell/strings.ts +89 -0
  160. package/src/web/client/theme/handoff.ts +43 -0
  161. package/src/web/client/theme/palette.ts +109 -0
  162. package/src/web/client/theme/registry.ts +130 -0
  163. package/src/web/client/theme/storage.ts +72 -0
  164. package/src/web/client/theme/strings.ts +123 -0
  165. package/src/web/client/theme/studio.ts +366 -0
  166. package/src/web/client/ui/actions.ts +109 -0
  167. package/src/web/client/ui/data.ts +185 -0
  168. package/src/web/client/ui/dom.ts +23 -0
  169. package/src/web/client/ui/fields.ts +191 -0
  170. package/src/web/client/ui/format.ts +75 -0
  171. package/src/web/client/ui/icons.ts +183 -0
  172. package/src/web/client/ui/images.ts +91 -0
  173. package/src/web/client/ui/index.ts +89 -0
  174. package/src/web/client/ui/lamp.ts +125 -0
  175. package/src/web/client/ui/log.ts +98 -0
  176. package/src/web/client/ui/overlay.ts +184 -0
  177. package/src/web/client/ui/page.ts +8 -0
  178. package/src/web/client/ui/prompt-input.tsx +311 -0
  179. package/src/web/client/ui/sheet.ts +131 -0
  180. package/src/web/client/ui/strings.ts +71 -0
  181. package/src/web/console-pages.ts +367 -0
  182. package/src/web/files.ts +107 -0
  183. package/src/web/path-picker.ts +296 -0
  184. package/src/web/public/index.html +17 -0
  185. package/src/web/public/styles.css +1715 -0
  186. package/src/web/server.ts +1883 -0
  187. package/src/web/shared/client-panel.ts +603 -0
  188. package/src/web/shared/console-protocol.ts +560 -0
  189. package/src/web/shared/css.d.ts +1 -0
  190. package/src/web/shared/path-picker.ts +23 -0
  191. package/src/web/shared/theme.ts +256 -0
  192. package/src/web/theme-store.ts +37 -0
  193. package/src/world.ts +449 -0
  194. package/src/worlds/bilibili/ENV_PROMPT.md +5 -0
  195. package/src/worlds/bilibili/README.md +176 -0
  196. package/src/worlds/bilibili/audience-admission.ts +995 -0
  197. package/src/worlds/bilibili/client.ts +528 -0
  198. package/src/worlds/bilibili/coalescing-buffer.ts +125 -0
  199. package/src/worlds/bilibili/config.ts +185 -0
  200. package/src/worlds/bilibili/console/client.ts +202 -0
  201. package/src/worlds/bilibili/definition.ts +35 -0
  202. package/src/worlds/bilibili/gift-frame.ts +131 -0
  203. package/src/worlds/bilibili/normalize.ts +452 -0
  204. package/src/worlds/bilibili/overlay/announcement.ts +76 -0
  205. package/src/worlds/bilibili/overlay/assets.ts +82 -0
  206. package/src/worlds/bilibili/overlay/model.ts +494 -0
  207. package/src/worlds/bilibili/overlay/project.ts +202 -0
  208. package/src/worlds/bilibili/overlay/server.ts +286 -0
  209. package/src/worlds/bilibili/overlay/types.ts +217 -0
  210. package/src/worlds/bilibili/overlay/web/app.js +690 -0
  211. package/src/worlds/bilibili/overlay/web/editor.css +297 -0
  212. package/src/worlds/bilibili/overlay/web/editor.html +102 -0
  213. package/src/worlds/bilibili/overlay/web/editor.js +2116 -0
  214. package/src/worlds/bilibili/overlay/web/overlay.html +13 -0
  215. package/src/worlds/bilibili/overlay/web/styles.css +258 -0
  216. package/src/worlds/bilibili/protobuf.ts +123 -0
  217. package/src/worlds/bilibili/wire.ts +94 -0
  218. package/src/worlds/bilibili/world.ts +1389 -0
  219. package/src/worlds/console-fixture/ENV_PROMPT.md +1 -0
  220. package/src/worlds/console-fixture/console/client.ts +72 -0
  221. package/src/worlds/console-fixture/world.ts +73 -0
  222. package/src/worlds/index.ts +15 -0
  223. package/src/worlds/minecraft/ENV_PROMPT.md +32 -0
  224. package/src/worlds/minecraft/ENV_PROMPT_CAMERA.md +2 -0
  225. package/src/worlds/minecraft/LICENSE-mineflayer-pathfinder.txt +21 -0
  226. package/src/worlds/minecraft/README.md +917 -0
  227. package/src/worlds/minecraft/blueprint-plan.ts +915 -0
  228. package/src/worlds/minecraft/blueprint-registry.ts +566 -0
  229. package/src/worlds/minecraft/blueprint-repair.ts +236 -0
  230. package/src/worlds/minecraft/blueprint-resource.ts +272 -0
  231. package/src/worlds/minecraft/blueprint.ts +492 -0
  232. package/src/worlds/minecraft/body-lease.ts +340 -0
  233. package/src/worlds/minecraft/bridge.ts +749 -0
  234. package/src/worlds/minecraft/check.ts +651 -0
  235. package/src/worlds/minecraft/chests.ts +292 -0
  236. package/src/worlds/minecraft/client-launch.ts +272 -0
  237. package/src/worlds/minecraft/client-options.ts +85 -0
  238. package/src/worlds/minecraft/client-skins.ts +164 -0
  239. package/src/worlds/minecraft/client-window.ps1 +91 -0
  240. package/src/worlds/minecraft/client.ts +423 -0
  241. package/src/worlds/minecraft/combat-context.ts +109 -0
  242. package/src/worlds/minecraft/combat.ts +1552 -0
  243. package/src/worlds/minecraft/config.ts +499 -0
  244. package/src/worlds/minecraft/console/access.ts +218 -0
  245. package/src/worlds/minecraft/console/client.ts +244 -0
  246. package/src/worlds/minecraft/console/log.ts +110 -0
  247. package/src/worlds/minecraft/console/mount.ts +420 -0
  248. package/src/worlds/minecraft/console/skin.ts +257 -0
  249. package/src/worlds/minecraft/console/style.css +154 -0
  250. package/src/worlds/minecraft/console/world.ts +449 -0
  251. package/src/worlds/minecraft/deaths.ts +135 -0
  252. package/src/worlds/minecraft/definition.ts +18 -0
  253. package/src/worlds/minecraft/engine-child.ts +274 -0
  254. package/src/worlds/minecraft/engine-ipc.ts +98 -0
  255. package/src/worlds/minecraft/entity-facts.ts +132 -0
  256. package/src/worlds/minecraft/escape.ts +407 -0
  257. package/src/worlds/minecraft/executor.ts +14494 -0
  258. package/src/worlds/minecraft/explored.ts +156 -0
  259. package/src/worlds/minecraft/geometry.ts +205 -0
  260. package/src/worlds/minecraft/goal-plan.ts +647 -0
  261. package/src/worlds/minecraft/item-break.ts +126 -0
  262. package/src/worlds/minecraft/item-facts.ts +146 -0
  263. package/src/worlds/minecraft/item-pick.ts +67 -0
  264. package/src/worlds/minecraft/level-dat.ts +176 -0
  265. package/src/worlds/minecraft/log.ts +132 -0
  266. package/src/worlds/minecraft/mineflayer-fixes.ts +1180 -0
  267. package/src/worlds/minecraft/names.ts +377 -0
  268. package/src/worlds/minecraft/pathfinder-lib.d.ts +13 -0
  269. package/src/worlds/minecraft/pathfinder-perf.ts +796 -0
  270. package/src/worlds/minecraft/piglin.ts +49 -0
  271. package/src/worlds/minecraft/policy.ts +547 -0
  272. package/src/worlds/minecraft/precheck.ts +655 -0
  273. package/src/worlds/minecraft/proxy.ts +445 -0
  274. package/src/worlds/minecraft/ranged.ts +635 -0
  275. package/src/worlds/minecraft/readouts.ts +102 -0
  276. package/src/worlds/minecraft/round.ts +26 -0
  277. package/src/worlds/minecraft/search-observation.ts +92 -0
  278. package/src/worlds/minecraft/server-config.ts +467 -0
  279. package/src/worlds/minecraft/server.ts +658 -0
  280. package/src/worlds/minecraft/show.ts +84 -0
  281. package/src/worlds/minecraft/skills.ts +1763 -0
  282. package/src/worlds/minecraft/terrain.ts +1735 -0
  283. package/src/worlds/minecraft/window.ts +96 -0
  284. package/src/worlds/minecraft/works.ts +179 -0
  285. package/src/worlds/minecraft/world.ts +6438 -0
  286. package/src/worlds/qq/ENV_PROMPT.md +5 -0
  287. package/src/worlds/qq/config.ts +108 -0
  288. package/src/worlds/qq/console/client.ts +126 -0
  289. package/src/worlds/qq/console/events.ts +155 -0
  290. package/src/worlds/qq/console/gate.ts +232 -0
  291. package/src/worlds/qq/console/roster.ts +203 -0
  292. package/src/worlds/qq/console/style.css +65 -0
  293. package/src/worlds/qq/conversation.ts +28 -0
  294. package/src/worlds/qq/definition.ts +62 -0
  295. package/src/worlds/qq/driver.ts +375 -0
  296. package/src/worlds/qq/history-tools.ts +209 -0
  297. package/src/worlds/qq/normalize.ts +323 -0
  298. package/src/worlds/qq/qface-map.ts +282 -0
  299. package/src/worlds/qq/vision-prompt.ts +27 -0
  300. package/src/worlds/qq/vision.ts +642 -0
  301. package/src/worlds/qq/vlm.ts +142 -0
  302. package/src/worlds/qq/world.ts +1503 -0
  303. package/src/worlds/terminal/ENV_PROMPT.md +11 -0
  304. package/src/worlds/terminal/config.ts +9 -0
  305. package/src/worlds/terminal/definition.ts +14 -0
  306. package/src/worlds/terminal/world.ts +700 -0
  307. package/src/worlds/websearch/ENV_PROMPT.md +1 -0
  308. package/src/worlds/websearch/brave-client.ts +261 -0
  309. package/src/worlds/websearch/config.ts +75 -0
  310. package/src/worlds/websearch/definition.ts +20 -0
  311. package/src/worlds/websearch/world.ts +171 -0
@@ -0,0 +1,297 @@
1
+ :root {
2
+ color-scheme: light;
3
+ --paper: #f7f5ef;
4
+ --paper-2: #efede6;
5
+ --sheet: #fbfaf6;
6
+ --sheet-2: #f2f0e9;
7
+ --sheet-3: #ebe8df;
8
+ --ink: #33312d;
9
+ --ink-soft: #6f6b63;
10
+ --ink-dim: #918c82;
11
+ --line: #e5e1d8;
12
+ --line-2: #d7d2c8;
13
+ --line-strong: #bdb6aa;
14
+ --accent: #c15f3c;
15
+ --accent-2: #9b5035;
16
+ --danger: #aa4d43;
17
+ --on-accent: #fff8f0;
18
+ --canvas: #222a35;
19
+ --canvas-2: #2b3543;
20
+ --canvas-line: #111821;
21
+ --sans: -apple-system, "Segoe UI", "PingFang SC", "Microsoft YaHei", "Noto Sans CJK SC", sans-serif;
22
+ --serif: "Songti SC", "STSong", "Noto Serif SC", Georgia, serif;
23
+ --mono: ui-monospace, "Cascadia Mono", "Sarasa Mono SC", Consolas, monospace;
24
+ }
25
+
26
+ :root[data-color-mode="dark"] {
27
+ color-scheme: dark;
28
+ --paper: #1c1a17;
29
+ --paper-2: #24211d;
30
+ --sheet: #292621;
31
+ --sheet-2: #302c27;
32
+ --sheet-3: #38332d;
33
+ --ink: #eee9df;
34
+ --ink-soft: #bdb5a8;
35
+ --ink-dim: #8f877c;
36
+ --line: #3a352f;
37
+ --line-2: #4d473e;
38
+ --line-strong: #6b6256;
39
+ --accent: #d9815f;
40
+ --accent-2: #e4a085;
41
+ --on-accent: #241711;
42
+ --danger: #df8e82;
43
+ }
44
+
45
+ * { box-sizing: border-box; }
46
+ html, body { width: 100%; height: 100%; }
47
+ body { margin: 0; overflow: hidden; background: var(--paper); color: var(--ink); font: 14px/1.55 var(--sans); }
48
+ button, input, select, textarea { font: inherit; color: inherit; }
49
+ button, select { cursor: pointer; }
50
+ button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible {
51
+ outline: 2px solid color-mix(in srgb, var(--accent) 70%, var(--sheet));
52
+ outline-offset: 1px;
53
+ }
54
+ ::-webkit-scrollbar { width: 10px; height: 10px; }
55
+ ::-webkit-scrollbar-thumb { background: var(--line-2); border: 3px solid transparent; background-clip: padding-box; border-radius: 7px; }
56
+ ::-webkit-scrollbar-track { background: transparent; }
57
+
58
+ .editor-shell { display: grid; grid-template-rows: 58px minmax(0, 1fr); width: 100%; height: 100%; }
59
+ .topbar {
60
+ display: flex; align-items: center; gap: 10px; padding: 0 16px 0 14px;
61
+ border-bottom: 1px solid var(--line-2); background: var(--sheet); z-index: 20;
62
+ }
63
+ .brandmark { display: flex; align-items: center; gap: 10px; min-width: 262px; }
64
+ .brandmark > span:last-child { display: flex; align-items: baseline; gap: 9px; }
65
+ .brandmark strong { font: 600 18px/1 var(--serif); letter-spacing: .04em; }
66
+ .brandmark small { color: var(--ink-dim); font: 10px/1 var(--mono); letter-spacing: .12em; }
67
+ .brand-icon {
68
+ width: 30px; height: 30px; display: grid; place-items: center; background: var(--accent); color: var(--on-accent);
69
+ font: 700 16px/1 var(--serif); clip-path: polygon(0 0, 86% 0, 100% 14%, 100% 100%, 14% 100%, 0 86%);
70
+ }
71
+ .history-actions, .preview-actions, .canvas-tools { display: flex; align-items: center; gap: 6px; }
72
+ .preview-actions { color: var(--ink-dim); font-size: 12px; margin-right: 6px; }
73
+ .top-spacer, .rail-spacer { flex: 1; }
74
+ .icon-button, .square-button, .quiet-button, .zoom-value, .primary-button {
75
+ min-height: 31px; border: 1px solid var(--line-2); background: var(--sheet-2); border-radius: 7px;
76
+ transition: background .12s, border-color .12s, color .12s, transform .12s;
77
+ }
78
+ .icon-button:hover, .square-button:hover, .quiet-button:hover, .zoom-value:hover { background: var(--sheet); border-color: var(--line-strong); }
79
+ .icon-button { width: 32px; padding: 0; font: 18px/1 var(--sans); }
80
+ .icon-button:disabled { opacity: .35; cursor: default; }
81
+ .square-button { width: 34px; font-size: 19px; }
82
+ .quiet-button { padding: 5px 11px; color: var(--ink-soft); }
83
+ .link-button { display: inline-flex; align-items: center; color: var(--ink-soft); text-decoration: none; }
84
+ .primary-button { padding: 6px 17px; background: var(--accent); border-color: var(--accent); color: var(--on-accent); font-weight: 600; }
85
+ .primary-button:hover { background: var(--accent-2); }
86
+ .primary-button:disabled { opacity: .55; cursor: wait; }
87
+ .save-state { min-width: 105px; display: inline-flex; align-items: center; justify-content: flex-end; gap: 7px; color: var(--ink-soft); font-size: 12px; }
88
+ .save-state i { width: 7px; height: 7px; border-radius: 50%; background: var(--ink-dim); }
89
+ .save-state.dirty i { background: #b67928; }
90
+ .save-state.saved i { background: #4f7145; }
91
+ .save-state.error i { background: var(--danger); }
92
+
93
+ .editor-main { min-height: 0; display: grid; grid-template-columns: 76px 388px minmax(0, 1fr); }
94
+ .mode-rail {
95
+ min-height: 0; display: flex; flex-direction: column; gap: 5px; padding: 14px 8px 10px;
96
+ background: var(--paper-2); border-right: 1px solid var(--line-2);
97
+ }
98
+ .mode-button {
99
+ position: relative; display: flex; flex-direction: column; align-items: center; gap: 4px; padding: 9px 3px 8px;
100
+ border: 1px solid transparent; background: transparent; color: var(--ink-soft); border-radius: 8px; font-size: 11px;
101
+ }
102
+ .mode-button:hover { background: var(--sheet-2); color: var(--ink); }
103
+ .mode-button.active { background: var(--sheet); border-color: var(--line); color: var(--accent); }
104
+ .mode-button.active::before { content: ""; position: absolute; left: -9px; top: 8px; bottom: 8px; width: 3px; background: var(--accent); }
105
+ .mode-glyph { font: 20px/1.1 var(--mono); }
106
+
107
+ .inspector-shell { min-width: 0; min-height: 0; display: flex; flex-direction: column; background: var(--sheet); border-right: 1px solid var(--line-2); }
108
+ .inspector-head { height: 72px; flex: none; display: flex; align-items: center; justify-content: space-between; padding: 13px 18px; border-bottom: 1px solid var(--line); }
109
+ .inspector-head p { margin: 0 0 2px; color: var(--accent); font: 9px/1 var(--mono); letter-spacing: .18em; }
110
+ .inspector-head h1 { margin: 0; font: 600 19px/1.3 var(--serif); letter-spacing: .03em; }
111
+ .inspector-scroll { position: relative; min-height: 0; overflow: auto; padding: 12px 14px 32px; }
112
+ .section { margin-bottom: 14px; border: 1px solid var(--line); background: var(--sheet); }
113
+ .section > header { display: flex; align-items: center; gap: 8px; min-height: 37px; padding: 8px 10px; border-bottom: 1px solid var(--line); background: var(--sheet-2); }
114
+ .section > header strong { font: 600 13px/1.2 var(--serif); letter-spacing: .03em; }
115
+ .section > header small { color: var(--ink-dim); margin-left: auto; }
116
+ .section-body { padding: 10px; }
117
+ .section-body.flush { padding: 0; }
118
+ .resource-section {
119
+ position: sticky; top: 0; z-index: 6; display: flex; flex-direction: column;
120
+ max-height: min(36vh, 330px); box-shadow: 0 7px 12px color-mix(in srgb, var(--ink) 6%, transparent);
121
+ }
122
+ .resource-section > header { flex: none; }
123
+ .resource-section > .section-body { min-height: 0; overflow: auto; }
124
+ .resource-list { display: flex; flex-direction: column; }
125
+ .resource-row {
126
+ display: grid; grid-template-columns: 32px minmax(0, 1fr) auto; align-items: center; gap: 9px; min-height: 49px;
127
+ padding: 7px 10px; border: 0; border-bottom: 1px solid var(--line); background: var(--sheet); text-align: left;
128
+ }
129
+ .resource-row:last-child { border-bottom: 0; }
130
+ .resource-row:hover { background: var(--sheet-2); }
131
+ .resource-row.active { background: color-mix(in srgb, var(--accent) 8%, var(--sheet)); box-shadow: inset 3px 0 var(--accent); }
132
+ .resource-row .swatch { width: 27px; height: 27px; border: 1px solid var(--line-2); background-image: linear-gradient(45deg,#ddd 25%,transparent 25%),linear-gradient(-45deg,#ddd 25%,transparent 25%),linear-gradient(45deg,transparent 75%,#ddd 75%),linear-gradient(-45deg,transparent 75%,#ddd 75%); background-size: 8px 8px; }
133
+ .resource-row .swatch > i { display: block; width: 100%; height: 100%; }
134
+ .resource-row .resource-icon { width: 27px; height: 27px; display: grid; place-items: center; border: 1px solid var(--line-2); color: var(--accent); font: 14px var(--mono); }
135
+ .resource-copy { min-width: 0; }
136
+ .resource-copy strong, .resource-copy span { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
137
+ .resource-copy strong { font-size: 13px; font-weight: 600; }
138
+ .resource-copy span { color: var(--ink-dim); font: 10px/1.4 var(--mono); margin-top: 2px; }
139
+ .row-badge { padding: 2px 5px; border: 1px solid var(--line-2); color: var(--ink-dim); font-size: 9px; letter-spacing: .05em; }
140
+ .row-badge.hidden-badge { color: var(--danger); }
141
+ .placeholder { padding: 23px 14px; color: var(--ink-dim); text-align: center; }
142
+ .button-row { display: flex; flex-wrap: wrap; align-items: center; gap: 7px; }
143
+ .button-row.split { justify-content: space-between; }
144
+ .inline-warning { display: grid; gap: 8px; margin-bottom: 10px; padding: 10px; border: 1px solid color-mix(in srgb, var(--danger) 42%, var(--line)); background: color-mix(in srgb, var(--danger) 7%, var(--paper)); color: var(--danger); font-size: 11px; line-height: 1.45; }
145
+ .small-button { min-height: 28px; padding: 4px 9px; border: 1px solid var(--line-2); background: var(--sheet-2); border-radius: 6px; font-size: 12px; }
146
+ .small-button:hover { background: var(--sheet); border-color: var(--line-strong); }
147
+ .small-button.danger { color: var(--danger); }
148
+ .quiet-button.hidden { display: none; }
149
+ .small-button.accent { background: color-mix(in srgb, var(--accent) 9%, var(--sheet)); border-color: color-mix(in srgb, var(--accent) 35%, var(--line)); color: var(--accent-2); }
150
+ .small-button:disabled { opacity: .4; cursor: default; }
151
+
152
+ .field { display: grid; grid-template-columns: 112px minmax(0, 1fr); align-items: center; gap: 10px; min-height: 34px; margin: 5px 0; }
153
+ .field.stack { display: block; }
154
+ .field > label, .field > .field-caption, .field-label { color: var(--ink-soft); font-size: 12px; }
155
+ .field.stack > label { display: block; margin-bottom: 5px; }
156
+ .field input:not([type="checkbox"]):not([type="range"]), .field select, .field textarea, .inline-input {
157
+ width: 100%; min-width: 0; border: 1px solid var(--line-2); border-radius: 5px; background: var(--sheet); padding: 5px 7px;
158
+ }
159
+ .field input[type="number"] { font-family: var(--mono); font-size: 12px; }
160
+ .field textarea { min-height: 74px; resize: vertical; }
161
+ .field input[type="range"] { width: 100%; accent-color: var(--accent); }
162
+ .field-note { color: var(--ink-dim); font-size: 11px; margin: 3px 0 7px 122px; }
163
+ .check-field { display: flex; align-items: center; gap: 8px; min-height: 31px; color: var(--ink-soft); font-size: 12px; }
164
+ .check-field input { accent-color: var(--accent); }
165
+ .segmented { display: flex; min-width: 0; }
166
+ .segmented button { flex: 1; padding: 5px 7px; border: 1px solid var(--line-2); border-right: 0; background: var(--sheet-2); color: var(--ink-soft); font-size: 11px; }
167
+ .segmented button:last-child { border-right: 1px solid var(--line-2); }
168
+ .segmented button.active { background: var(--ink); color: var(--sheet); border-color: var(--ink); }
169
+ .divider { height: 1px; background: var(--line); margin: 10px 0; }
170
+
171
+ .color-control { display: grid; grid-template-columns: 42px minmax(0, 1fr); gap: 8px; align-items: center; }
172
+ .color-chip { position: relative; width: 42px; height: 31px; overflow: hidden; border: 1px solid var(--line-2); background-color: var(--sheet); background-image: linear-gradient(45deg,#d5d2ca 25%,transparent 25%),linear-gradient(-45deg,#d5d2ca 25%,transparent 25%),linear-gradient(45deg,transparent 75%,#d5d2ca 75%),linear-gradient(-45deg,transparent 75%,#d5d2ca 75%); background-position: 0 0,0 6px,6px -6px,-6px 0; background-size: 12px 12px; }
173
+ .color-chip input { position: absolute; inset: -8px; width: 58px; height: 47px; border: 0; padding: 0; opacity: 0; }
174
+ .color-chip i { position: absolute; inset: 0; pointer-events: none; }
175
+ .color-values { display: grid; grid-template-columns: minmax(0, 1fr) 72px; gap: 6px; }
176
+ .color-values input { min-width: 0; font: 11px var(--mono); text-transform: uppercase; }
177
+ .alpha-wrap { position: relative; display: flex; align-items: center; }
178
+ .alpha-wrap::before { content: "A%"; position: absolute; left: 6px; z-index: 1; color: var(--ink-dim); font: 9px var(--mono); pointer-events: none; }
179
+ .alpha-wrap input { padding-left: 18px !important; text-align: right; }
180
+ .alpha-slider { display: block; width: calc(100% - 50px); height: 16px; margin: 5px 0 0 50px; accent-color: var(--accent); }
181
+ .rgba-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; margin: 4px 0 0 50px; }
182
+ .rgba-grid label { display: grid; grid-template-columns: 12px minmax(0,1fr); align-items: center; color: var(--ink-dim); font: 9px var(--mono); }
183
+ .rgba-grid input { width: 100%; min-width: 0; border: 1px solid var(--line); padding: 3px 2px; text-align: center; font: 10px var(--mono); }
184
+
185
+ .font-preview { min-height: 68px; display: flex; flex-direction: column; justify-content: center; gap: 3px; padding: 9px; overflow: hidden; border: 1px solid var(--line); background: var(--sheet-2); }
186
+ .font-preview span { white-space: nowrap; text-overflow: ellipsis; overflow: hidden; }
187
+ .subtabs { display: grid; grid-template-columns: repeat(3, 1fr); margin-bottom: 10px; }
188
+ .subtabs button { border: 1px solid var(--line); border-right: 0; padding: 6px 5px; background: var(--sheet-2); color: var(--ink-soft); font-size: 11px; }
189
+ .subtabs button:last-child { border-right: 1px solid var(--line); }
190
+ .subtabs button.active { color: var(--accent); background: var(--sheet); box-shadow: inset 0 -2px var(--accent); }
191
+
192
+ .asset-grid { display: grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: 7px; }
193
+ .asset-cell { position: relative; min-width: 0; }
194
+ .asset-card { position: relative; aspect-ratio: 1; padding: 0; overflow: hidden; border: 1px solid var(--line-2); background: var(--sheet-2); }
195
+ .asset-cell .asset-card { width: 100%; }
196
+ .asset-card img { width: 100%; height: 100%; display: block; object-fit: contain; }
197
+ .asset-card.active { outline: 2px solid var(--accent); outline-offset: -2px; }
198
+ .asset-card span { position: absolute; left: 3px; right: 3px; bottom: 3px; padding: 2px 3px; overflow: hidden; color: white; background: rgba(0,0,0,.58); font: 8px var(--mono); text-overflow: ellipsis; white-space: nowrap; }
199
+ .asset-remove { position: absolute; z-index: 3; right: 3px; top: 3px; width: 20px; height: 20px; padding: 0; border: 1px solid rgba(255,255,255,.55); background: rgba(40,32,29,.72); color: white; line-height: 18px; }
200
+ .asset-remove:hover { background: var(--danger); }
201
+ .file-button input { display: none; }
202
+
203
+ .slice-source { position: relative; min-height: 130px; display: grid; place-items: center; overflow: hidden; border: 1px solid var(--line-2); background-color: #dad7cf; background-image: linear-gradient(45deg,#c9c6bd 25%,transparent 25%),linear-gradient(-45deg,#c9c6bd 25%,transparent 25%),linear-gradient(45deg,transparent 75%,#c9c6bd 75%),linear-gradient(-45deg,transparent 75%,#c9c6bd 75%); background-size: 14px 14px; }
204
+ .slice-image-wrap { position: relative; max-width: 100%; max-height: 240px; line-height: 0; }
205
+ .slice-image-wrap img { display: block; max-width: 100%; max-height: 240px; object-fit: contain; }
206
+ .slice-guide { position: absolute; z-index: 2; background: #ff5e42; box-shadow: 0 0 0 1px rgba(255,255,255,.8); touch-action: none; }
207
+ .slice-guide.vertical { top: 0; bottom: 0; width: 2px; cursor: ew-resize; }
208
+ .slice-guide.horizontal { left: 0; right: 0; height: 2px; cursor: ns-resize; }
209
+ .slice-guide::after { content: ""; position: absolute; inset: -6px; }
210
+ .slice-values { display: grid; grid-template-columns: repeat(4,1fr); gap: 6px; margin-top: 8px; }
211
+ .slice-values label { color: var(--ink-dim); font: 9px var(--mono); }
212
+ .slice-values input { width: 100%; margin-top: 2px; border: 1px solid var(--line-2); padding: 4px; text-align: center; font: 11px var(--mono); }
213
+ .nine-preview { display: grid; place-items: center; height: 142px; min-width: 80px; margin-top: 8px; border-style: solid; color: var(--ink-soft); background: color-mix(in srgb, var(--sheet) 78%, transparent); resize: both; overflow: auto; }
214
+ .nine-preview span { font-size: 11px; pointer-events: none; }
215
+
216
+ .rule-card { padding: 8px; margin: 7px 0; border-left: 2px solid var(--accent); background: var(--sheet-2); }
217
+ .rule-line { display: grid; grid-template-columns: 1fr 1fr 1fr auto; gap: 5px; align-items: center; }
218
+ .rule-line select, .rule-line input { width: 100%; min-width: 0; border: 1px solid var(--line-2); padding: 4px; background: var(--sheet); font-size: 10px; }
219
+ .rule-children { padding-left: 9px; border-left: 1px solid var(--line-2); }
220
+
221
+ .canvas-workspace { min-width: 0; min-height: 0; display: grid; grid-template-rows: 48px minmax(0, 1fr) 31px; background: var(--canvas); }
222
+ .canvas-bar, .canvas-status { display: flex; align-items: center; gap: 12px; padding: 0 14px; background: var(--sheet); border-bottom: 1px solid var(--line-2); }
223
+ .canvas-meta { display: flex; align-items: baseline; gap: 9px; }
224
+ .canvas-meta strong { font: 600 13px var(--serif); }
225
+ .canvas-meta span { color: var(--ink-dim); font: 10px var(--mono); }
226
+ .canvas-tools { margin-left: auto; }
227
+ .wall-toggle { width: 64px; }
228
+ .wall-toggle button { min-width: 0; padding: 4px 7px; }
229
+ .compact-field { display: inline-flex; align-items: center; gap: 6px; color: var(--ink-dim); font-size: 11px; }
230
+ .compact-field select { border: 1px solid var(--line-2); background: var(--sheet-2); padding: 4px 6px; }
231
+ .zoom-value { min-width: 54px; padding: 0 8px; color: var(--ink-soft); font: 10px var(--mono); }
232
+ .canvas-viewport {
233
+ position: relative; min-width: 0; min-height: 0; overflow: auto; display: grid; place-items: center; padding: 56px;
234
+ --wall-canvas: #222a35; --wall-dot: #465365; --wall-ink: #ffffff;
235
+ background-color: var(--wall-canvas); background-image: radial-gradient(circle, var(--wall-dot) 1px, transparent 1px); background-size: 18px 18px;
236
+ }
237
+ .canvas-viewport[data-wall-mode="light"] { --wall-canvas: #e4e7e8; --wall-dot: #bcc2c6; --wall-ink: #182027; }
238
+ .canvas-viewport[data-wall-mode="dark"] { --wall-canvas: #222a35; --wall-dot: #465365; --wall-ink: #ffffff; }
239
+ .canvas-sizer { position: relative; flex: none; box-shadow: 0 18px 50px rgba(0,0,0,.34); }
240
+ .canvas-shell { position: absolute; left: 0; top: 0; transform-origin: left top; background: transparent; }
241
+ #preview-frame { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; pointer-events: none; background: transparent; }
242
+ .interaction-layer { position: absolute; inset: 0; overflow: visible; }
243
+ .placement-box { position: absolute; border: 2px solid transparent; cursor: move; touch-action: none; }
244
+ .placement-box:hover { border-color: color-mix(in srgb, var(--wall-ink) 50%, transparent); }
245
+ .placement-box.selected { border-color: #ff7359; box-shadow: 0 0 0 1px rgba(0,0,0,.45), inset 0 0 0 1px rgba(255,255,255,.35); }
246
+ .placement-box.locked { cursor: default; border-style: dashed; }
247
+ .placement-label { position: absolute; left: -2px; top: -23px; max-width: 180px; padding: 2px 6px; overflow: hidden; color: white; background: #b84e38; font: 11px/17px var(--sans); white-space: nowrap; text-overflow: ellipsis; pointer-events: none; }
248
+ .placement-box:not(.selected) .placement-label { display: none; }
249
+ .resize-handle { position: absolute; width: 10px; height: 10px; border: 1px solid #8b3528; background: #fff9f1; }
250
+ .placement-box:not(.selected) .resize-handle, .placement-box.locked .resize-handle { display: none; }
251
+ .placement-hotspot { position: absolute; padding: 0; border: 2px solid transparent; background: transparent; cursor: pointer; }
252
+ .placement-hotspot:hover { border-color: color-mix(in srgb, var(--wall-ink) 62%, transparent); background: color-mix(in srgb, var(--wall-ink) 4%, transparent); }
253
+ .placement-hotspot.selected { border-color: rgba(255,115,89,.48); }
254
+ .resize-handle[data-dir="nw"] { left: -6px; top: -6px; cursor: nwse-resize; }
255
+ .resize-handle[data-dir="n"] { left: calc(50% - 5px); top: -6px; cursor: ns-resize; }
256
+ .resize-handle[data-dir="ne"] { right: -6px; top: -6px; cursor: nesw-resize; }
257
+ .resize-handle[data-dir="e"] { right: -6px; top: calc(50% - 5px); cursor: ew-resize; }
258
+ .resize-handle[data-dir="se"] { right: -6px; bottom: -6px; cursor: nwse-resize; }
259
+ .resize-handle[data-dir="s"] { left: calc(50% - 5px); bottom: -6px; cursor: ns-resize; }
260
+ .resize-handle[data-dir="sw"] { left: -6px; bottom: -6px; cursor: nesw-resize; }
261
+ .resize-handle[data-dir="w"] { left: -6px; top: calc(50% - 5px); cursor: ew-resize; }
262
+ .snap-line { position: absolute; z-index: 10; pointer-events: none; background: #50c8ff; box-shadow: 0 0 2px #072f43; }
263
+ .snap-line.vertical { top: 0; bottom: 0; width: 1px; }
264
+ .snap-line.horizontal { left: 0; right: 0; height: 1px; }
265
+ .empty-overlay { position: absolute; inset: 0; z-index: 15; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; color: color-mix(in srgb, var(--wall-ink) 72%, transparent); background: var(--wall-canvas); }
266
+ .empty-overlay.hidden { display: none; }
267
+ .spinner { width: 25px; height: 25px; border: 2px solid color-mix(in srgb, var(--wall-ink) 20%, transparent); border-top-color: var(--wall-ink); border-radius: 50%; animation: spin .8s linear infinite; }
268
+ @keyframes spin { to { transform: rotate(360deg); } }
269
+ .canvas-status { border: 0; border-top: 1px solid var(--line-2); color: var(--ink-soft); font: 10px var(--mono); }
270
+ .shortcut-hint { margin-left: auto; color: var(--ink-dim); }
271
+
272
+ .toast-stack { position: fixed; z-index: 100; right: 16px; bottom: 16px; display: flex; flex-direction: column; gap: 8px; pointer-events: none; }
273
+ .toast { max-width: 360px; padding: 10px 13px; border: 1px solid var(--line-2); background: var(--sheet); box-shadow: 0 8px 28px rgba(0,0,0,.18); color: var(--ink); animation: toast-in .15s ease-out; }
274
+ .toast.bad { border-left: 3px solid var(--danger); }
275
+ @keyframes toast-in { from { opacity: 0; transform: translateY(6px); } }
276
+ dialog { width: min(520px, calc(100vw - 40px)); border: 1px solid var(--line-2); background: var(--sheet); color: var(--ink); box-shadow: 0 24px 80px rgba(0,0,0,.28); padding: 0; }
277
+ dialog::backdrop { background: rgba(22,25,30,.52); }
278
+ dialog form { margin: 0; }
279
+ .modal-body { padding: 20px; }
280
+ .modal-body h2 { margin: 0 0 6px; font: 600 19px var(--serif); }
281
+ .modal-body p { color: var(--ink-soft); }
282
+ .modal-actions { display: flex; justify-content: flex-end; gap: 8px; padding: 12px 20px; border-top: 1px solid var(--line); background: var(--sheet-2); }
283
+
284
+ @media (max-width: 1050px) {
285
+ .editor-main { grid-template-columns: 68px 340px minmax(0, 1fr); }
286
+ .brandmark { min-width: 220px; }
287
+ .preview-actions { display: none; }
288
+ .shortcut-hint { display: none; }
289
+ }
290
+
291
+ @media (max-width: 780px) {
292
+ .editor-main { grid-template-columns: 62px minmax(270px, 42vw) minmax(360px, 1fr); overflow-x: auto; }
293
+ .brandmark small { display: none; }
294
+ .save-state > span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
295
+ .save-state { min-width: auto; }
296
+ .brandmark { min-width: auto; }
297
+ }
@@ -0,0 +1,102 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <title>Bilibili Overlay 编辑器</title>
7
+ <link rel="stylesheet" href="/editor/editor.css">
8
+ </head>
9
+ <body>
10
+ <div class="editor-shell">
11
+ <header class="topbar">
12
+ <div class="brandmark" aria-label="Bilibili Overlay 编辑器">
13
+ <span class="brand-icon">B</span>
14
+ <span><strong>Overlay 编辑器</strong><small>BILIBILI LIVE</small></span>
15
+ </div>
16
+ <div class="history-actions">
17
+ <button class="icon-button" id="undo" type="button" title="撤销 (Ctrl+Z)" aria-label="撤销">↶</button>
18
+ <button class="icon-button" id="redo" type="button" title="重做 (Ctrl+Shift+Z)" aria-label="重做">↷</button>
19
+ </div>
20
+ <div class="top-spacer"></div>
21
+ <div class="preview-actions" aria-label="试显事件">
22
+ <span>试显</span>
23
+ <button class="quiet-button" id="demo-danmaku" type="button">弹幕</button>
24
+ <button class="quiet-button" id="demo-gift" type="button">礼物</button>
25
+ </div>
26
+ <a class="quiet-button link-button" id="open-overlay" href="/overlay" target="_blank" rel="noreferrer">OBS 页面 ↗</a>
27
+ <span class="save-state" id="save-state" role="status" aria-live="polite"><i></i><span>正在连接</span></span>
28
+ <button class="quiet-button hidden" id="adopt-design" type="button">采用远端</button>
29
+ <button class="primary-button" id="save" type="button">保存设计</button>
30
+ </header>
31
+
32
+ <main class="editor-main">
33
+ <nav class="mode-rail" aria-label="编辑器工作区">
34
+ <button class="mode-button active" type="button" data-mode="styles" aria-current="page">
35
+ <span class="mode-glyph">◩</span><span>样式</span>
36
+ </button>
37
+ <button class="mode-button" type="button" data-mode="components">
38
+ <span class="mode-glyph">▣</span><span>组件</span>
39
+ </button>
40
+ <button class="mode-button" type="button" data-mode="layout">
41
+ <span class="mode-glyph">⌖</span><span>布局</span>
42
+ </button>
43
+ <div class="rail-spacer"></div>
44
+ <button class="mode-button" id="help" type="button" title="快捷键与使用说明">
45
+ <span class="mode-glyph">?</span><span>帮助</span>
46
+ </button>
47
+ </nav>
48
+
49
+ <aside class="inspector-shell">
50
+ <div class="inspector-head">
51
+ <div><p id="workspace-kicker">STYLE</p><h1 id="workspace-title">样式编辑器</h1></div>
52
+ <button class="square-button" id="create" type="button" title="新建">+</button>
53
+ </div>
54
+ <div class="inspector-scroll" id="inspector" aria-live="polite"></div>
55
+ </aside>
56
+
57
+ <section class="canvas-workspace">
58
+ <div class="canvas-bar">
59
+ <div class="canvas-meta">
60
+ <strong id="canvas-name">直播画布</strong>
61
+ <span id="canvas-size">1920 × 1080</span>
62
+ </div>
63
+ <div class="canvas-tools">
64
+ <div class="segmented wall-toggle" aria-label="画布背景墙">
65
+ <button type="button" data-wall-mode="light" title="浅色背景墙">浅</button>
66
+ <button type="button" data-wall-mode="dark" title="深色背景墙">深</button>
67
+ </div>
68
+ <label class="compact-field">网格
69
+ <select id="snap-grid">
70
+ <option value="1">1 px</option>
71
+ <option value="5">5 px</option>
72
+ <option value="10" selected>10 px</option>
73
+ <option value="20">20 px</option>
74
+ </select>
75
+ </label>
76
+ <button class="icon-button" id="zoom-out" type="button" aria-label="缩小">−</button>
77
+ <button class="zoom-value" id="zoom-reset" type="button" title="适合画布"><span id="zoom-label">适合</span></button>
78
+ <button class="icon-button" id="zoom-in" type="button" aria-label="放大">+</button>
79
+ </div>
80
+ </div>
81
+ <div class="canvas-viewport" id="canvas-viewport">
82
+ <div class="canvas-sizer" id="canvas-sizer">
83
+ <div class="canvas-shell" id="canvas-shell">
84
+ <iframe id="preview-frame" title="Overlay 实时草稿预览" data-src="/overlay?preview=1"></iframe>
85
+ <div class="interaction-layer" id="interaction-layer" aria-label="布局交互层"></div>
86
+ </div>
87
+ </div>
88
+ <div class="empty-overlay" id="loading"><span class="spinner"></span><strong>正在装入编辑器</strong></div>
89
+ </div>
90
+ <footer class="canvas-status">
91
+ <span id="selection-status">请选择一个对象</span>
92
+ <span class="shortcut-hint">拖动移动 · 八向缩放 · 方向键 1px · Shift 10px · Delete 删除</span>
93
+ </footer>
94
+ </section>
95
+ </main>
96
+ </div>
97
+
98
+ <div class="toast-stack" id="toasts" aria-live="assertive"></div>
99
+ <dialog id="modal"><form method="dialog"><div id="modal-content"></div></form></dialog>
100
+ <script src="/editor/editor.js"></script>
101
+ </body>
102
+ </html>