@volter/editor-core 0.5.57

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 (719) hide show
  1. package/BUNDLED_NOTICES +1527 -0
  2. package/LICENSE +686 -0
  3. package/LICENSE-APACHE +202 -0
  4. package/NOTICE +18 -0
  5. package/README.md +24 -0
  6. package/dist/build/chunk-JS4D4XZ3.js +175 -0
  7. package/dist/build/chunk-JS4D4XZ3.js.map +7 -0
  8. package/dist/build/vite-plugin-product-contributions.js +100 -0
  9. package/dist/build/vite-plugin-product-contributions.js.map +7 -0
  10. package/dist/build/vite-plugin-shared-react.js +25 -0
  11. package/dist/build/vite-plugin-shared-react.js.map +7 -0
  12. package/dist/build/vite-plugin-shared-three.js +86 -0
  13. package/dist/build/vite-plugin-shared-three.js.map +7 -0
  14. package/dist/server/chunk-KMJC7AGO.js +98 -0
  15. package/dist/server/chunk-KMJC7AGO.js.map +7 -0
  16. package/dist/server/chunk-PSILUMGS.js +46 -0
  17. package/dist/server/chunk-PSILUMGS.js.map +7 -0
  18. package/dist/server/frame-proxy.js +255 -0
  19. package/dist/server/frame-proxy.js.map +7 -0
  20. package/dist/server/open-browser.js +53 -0
  21. package/dist/server/open-browser.js.map +7 -0
  22. package/dist/server/process-shutdown.js +127 -0
  23. package/dist/server/process-shutdown.js.map +7 -0
  24. package/dist/server/session-registry.js +141 -0
  25. package/dist/server/session-registry.js.map +7 -0
  26. package/dist/server/spawn-opener.js +11 -0
  27. package/dist/server/spawn-opener.js.map +7 -0
  28. package/dist/server/worktree-identity.js +11 -0
  29. package/dist/server/worktree-identity.js.map +7 -0
  30. package/dist-server/packaged.mjs +88816 -0
  31. package/package.json +118 -0
  32. package/scripts/build-plugins.mjs +14 -0
  33. package/scripts/build-server.mjs +26 -0
  34. package/scripts/build-session.mjs +9 -0
  35. package/server/account-credentials.ts +60 -0
  36. package/server/account-service.ts +1434 -0
  37. package/server/adapter-region-includes.ts +47 -0
  38. package/server/asset-catalog-v2.ts +175 -0
  39. package/server/asset-history-snapshots.ts +196 -0
  40. package/server/asset-ledger-store.ts +195 -0
  41. package/server/asset-library-routes.ts +1595 -0
  42. package/server/blender-wasm-artifact.ts +430 -0
  43. package/server/boot-timings.ts +106 -0
  44. package/server/canonical-path.ts +50 -0
  45. package/server/checkout-workspace-preflight.ts +349 -0
  46. package/server/cloud-asset-catalog.ts +158 -0
  47. package/server/coding-inference-launch.ts +237 -0
  48. package/server/collaboration-account-client.ts +202 -0
  49. package/server/collaboration-attribution.ts +122 -0
  50. package/server/collaboration-session.ts +934 -0
  51. package/server/comfyui-bridge.ts +248 -0
  52. package/server/console-ledger.ts +499 -0
  53. package/server/creation-site-transform.ts +304 -0
  54. package/server/creation-site-write.ts +648 -0
  55. package/server/data-file-serialize.ts +158 -0
  56. package/server/editor-brand-html.ts +72 -0
  57. package/server/editor-control-socket.ts +366 -0
  58. package/server/editor-server-options.ts +190 -0
  59. package/server/editor-server.ts +1483 -0
  60. package/server/editor-sse.ts +352 -0
  61. package/server/engine-provenance.ts +169 -0
  62. package/server/engine-source-restart.ts +117 -0
  63. package/server/frame-bridge.ts +115 -0
  64. package/server/frame-proxy.ts +490 -0
  65. package/server/frame-workbench.ts +259 -0
  66. package/server/frontend-handoff.ts +229 -0
  67. package/server/game-globals-shadow.ts +113 -0
  68. package/server/gameplay-session-retention.ts +149 -0
  69. package/server/gameplay-sessions.ts +123 -0
  70. package/server/generation-jobs.ts +201 -0
  71. package/server/generation-reconciler.ts +137 -0
  72. package/server/generative-execution-context.ts +59 -0
  73. package/server/git-workflow.ts +612 -0
  74. package/server/harness-chat-caller.ts +36 -0
  75. package/server/harness-chat-service.ts +1681 -0
  76. package/server/idle-shutdown.ts +170 -0
  77. package/server/js-profiling-policy.ts +52 -0
  78. package/server/launcher-settings.ts +84 -0
  79. package/server/local-asset-catalog.ts +410 -0
  80. package/server/managed-account-defaults.ts +22 -0
  81. package/server/mock-control-plane.ts +540 -0
  82. package/server/model-import-conversion.ts +345 -0
  83. package/server/native-credential-store.ts +162 -0
  84. package/server/open-browser.ts +118 -0
  85. package/server/packaged.ts +1192 -0
  86. package/server/play-stall.ts +126 -0
  87. package/server/process-shutdown.ts +218 -0
  88. package/server/product-presets.ts +55 -0
  89. package/server/project-build-artifact.ts +95 -0
  90. package/server/project-components.ts +100 -0
  91. package/server/project-dependency-invalidation.ts +114 -0
  92. package/server/project-file-scan.ts +235 -0
  93. package/server/project-hmr-files.ts +215 -0
  94. package/server/project-install-roots.ts +210 -0
  95. package/server/project-kinds.ts +71 -0
  96. package/server/project-mcp-servers.ts +82 -0
  97. package/server/project-module-freshness.ts +109 -0
  98. package/server/project-module-instance.ts +375 -0
  99. package/server/project-optimize-deps-entries.ts +827 -0
  100. package/server/project-output-writer.ts +679 -0
  101. package/server/project-package-origin.ts +63 -0
  102. package/server/project-root-surface.ts +831 -0
  103. package/server/project-scratch-path.ts +47 -0
  104. package/server/project-script-hmr.ts +619 -0
  105. package/server/project-serving-plugins.ts +236 -0
  106. package/server/project-tools.ts +653 -0
  107. package/server/project-validation.ts +250 -0
  108. package/server/project-verbs.ts +127 -0
  109. package/server/project-view.ts +52 -0
  110. package/server/project-watch.ts +1134 -0
  111. package/server/project-work-coordinator.ts +288 -0
  112. package/server/provider-credentials.ts +331 -0
  113. package/server/recent-projects-store.ts +98 -0
  114. package/server/redact-secrets.ts +71 -0
  115. package/server/repository-presence.ts +267 -0
  116. package/server/routes/account.ts +357 -0
  117. package/server/routes/agents.ts +60 -0
  118. package/server/routes/assets.ts +154 -0
  119. package/server/routes/build.ts +13 -0
  120. package/server/routes/collaboration.ts +595 -0
  121. package/server/routes/configurations.ts +419 -0
  122. package/server/routes/context.ts +264 -0
  123. package/server/routes/control-plane.ts +1811 -0
  124. package/server/routes/logs.ts +272 -0
  125. package/server/routes/project-identity.ts +302 -0
  126. package/server/routes/project-open.ts +429 -0
  127. package/server/routes/project-source.ts +686 -0
  128. package/server/routes/project-state.ts +383 -0
  129. package/server/routes/relay.ts +1029 -0
  130. package/server/routes/route-helpers.ts +29 -0
  131. package/server/routes/served-modules.ts +138 -0
  132. package/server/routes/session-tabs.ts +196 -0
  133. package/server/routes/settings.ts +121 -0
  134. package/server/routes/share-control.ts +486 -0
  135. package/server/routes/themes.ts +116 -0
  136. package/server/routes/tools.ts +230 -0
  137. package/server/routes/worktrees.ts +595 -0
  138. package/server/scoped-game-css.ts +461 -0
  139. package/server/server-utils.ts +1194 -0
  140. package/server/session-product.ts +112 -0
  141. package/server/session-registry.ts +230 -0
  142. package/server/share-claims.ts +138 -0
  143. package/server/share-host.ts +526 -0
  144. package/server/share-session-gateway.ts +1362 -0
  145. package/server/share-tunnel.ts +123 -0
  146. package/server/spawn-opener.ts +74 -0
  147. package/server/support/play/log-format.ts +76 -0
  148. package/server/support/play/session-record.ts +199 -0
  149. package/server/support/project/build-discipline.ts +718 -0
  150. package/server/support/project/inspection-node.ts +119 -0
  151. package/server/support/project/log-naming.ts +54 -0
  152. package/server/support/project/provenance.ts +290 -0
  153. package/server/support/project/run-name.ts +18 -0
  154. package/server/support/project/session-journal.ts +830 -0
  155. package/server/support/project/shared.ts +327 -0
  156. package/server/tab-bootstrap.ts +62 -0
  157. package/server/tab-heartbeat.ts +477 -0
  158. package/server/tab-lifecycle.ts +550 -0
  159. package/server/tab-presence.ts +1508 -0
  160. package/server/team-agent-mirror.ts +95 -0
  161. package/server/twin-auth.ts +138 -0
  162. package/server/vendored-lock-recorder.ts +968 -0
  163. package/server/worktree-identity.ts +121 -0
  164. package/server/worktree-management.ts +398 -0
  165. package/server/worktree-retention.d.mts +13 -0
  166. package/server/worktree-retention.mjs +205 -0
  167. package/src/EditorContext.tsx +169 -0
  168. package/src/account.ts +333 -0
  169. package/src/action-registry.ts +532 -0
  170. package/src/active-product.ts +74 -0
  171. package/src/active-project.ts +155 -0
  172. package/src/adapter-editor-config.ts +25 -0
  173. package/src/adapter-observation.ts +49 -0
  174. package/src/animation/stage-transport.ts +379 -0
  175. package/src/animation/three-clips-subject.ts +172 -0
  176. package/src/api/asset-library-wire.ts +45 -0
  177. package/src/api/assets.ts +365 -0
  178. package/src/api/base.ts +10 -0
  179. package/src/api/build.ts +99 -0
  180. package/src/api/git-wire.ts +56 -0
  181. package/src/api/logs.ts +92 -0
  182. package/src/api/project-identity.ts +74 -0
  183. package/src/api/project-open.ts +355 -0
  184. package/src/api/project-source.ts +162 -0
  185. package/src/api/project-state.ts +107 -0
  186. package/src/api/relay.ts +268 -0
  187. package/src/api/settings.ts +36 -0
  188. package/src/api/themes.ts +45 -0
  189. package/src/api/worktrees.ts +257 -0
  190. package/src/asset-compare-core.ts +171 -0
  191. package/src/asset-compare.ts +294 -0
  192. package/src/asset-editor-context.tsx +100 -0
  193. package/src/asset-events.ts +97 -0
  194. package/src/asset-inspector-actions.ts +87 -0
  195. package/src/asset-preview-framing.ts +357 -0
  196. package/src/asset-preview.ts +2802 -0
  197. package/src/asset-selection-viewer-registry.ts +113 -0
  198. package/src/asset-selection.ts +133 -0
  199. package/src/asset-workflow/asset-capabilities.ts +344 -0
  200. package/src/asset-workflow/asset-import-jobs.ts +106 -0
  201. package/src/asset-workflow/asset-ledger-backend.ts +126 -0
  202. package/src/asset-workflow/asset-ledger.ts +156 -0
  203. package/src/asset-workflow/asset-materialization-report.ts +140 -0
  204. package/src/asset-workflow/asset-pack-manifest.ts +320 -0
  205. package/src/asset-workflow/asset-types.ts +142 -0
  206. package/src/asset-workflow/asset-workflow-quality.ts +61 -0
  207. package/src/asset-workflow/audio-preview-player.ts +193 -0
  208. package/src/asset-workflow/audio-waveform.ts +22 -0
  209. package/src/asset-workflow/cloud-asset-client.ts +263 -0
  210. package/src/asset-workflow/folder-preview.ts +345 -0
  211. package/src/asset-workflow/hosted-asset-materialization.ts +236 -0
  212. package/src/asset-workflow/image-view-scale.ts +32 -0
  213. package/src/asset-workflow/import-contract.ts +124 -0
  214. package/src/asset-workflow/ledger-write-lock.ts +244 -0
  215. package/src/asset-workflow/model-inspection.ts +818 -0
  216. package/src/asset-workflow/pixi-spritesheet.ts +197 -0
  217. package/src/asset-workflow/preview-resource-lifetime.ts +44 -0
  218. package/src/asset-workflow/project-asset-commands.ts +23 -0
  219. package/src/asset-workflow/project-asset-health.ts +139 -0
  220. package/src/asset-workflow/project-asset-operations.ts +154 -0
  221. package/src/asset-workflow/project-asset-roots.ts +68 -0
  222. package/src/asset-workflow/project-content.ts +288 -0
  223. package/src/asset-workflow/project-source-index.ts +550 -0
  224. package/src/asset-workflow/thumbnail-system.ts +256 -0
  225. package/src/authoring/active-adapter.ts +199 -0
  226. package/src/authoring/active-systems.ts +422 -0
  227. package/src/authoring/adapter-key.ts +18 -0
  228. package/src/authoring/authoring-asset-url.ts +27 -0
  229. package/src/authoring/bootstrap-state.ts +49 -0
  230. package/src/authoring/boundary-authoring-adapter.ts +184 -0
  231. package/src/authoring/breakpoint-state.ts +43 -0
  232. package/src/authoring/canvas-scene-guides.ts +76 -0
  233. package/src/authoring/component-instance-root.ts +171 -0
  234. package/src/authoring/composite-authoring-adapter.ts +2110 -0
  235. package/src/authoring/consumer-actions.ts +520 -0
  236. package/src/authoring/css-numeric-style.ts +97 -0
  237. package/src/authoring/design-time-layers.ts +849 -0
  238. package/src/authoring/design-time-mount-registry.ts +235 -0
  239. package/src/authoring/design-time-settle.ts +343 -0
  240. package/src/authoring/edit-mode-authoring.ts +619 -0
  241. package/src/authoring/eyedropper-session.ts +60 -0
  242. package/src/authoring/instance-source-menu-register.ts +13 -0
  243. package/src/authoring/instance-source-menu.ts +135 -0
  244. package/src/authoring/layered-pick.ts +183 -0
  245. package/src/authoring/live-gesture-lock.ts +50 -0
  246. package/src/authoring/live-object-transform.ts +62 -0
  247. package/src/authoring/mount-failure-report.ts +154 -0
  248. package/src/authoring/mounted-root-subjects.ts +144 -0
  249. package/src/authoring/no-authoring-adapter.ts +55 -0
  250. package/src/authoring/null-inspection-subjects.tsx +75 -0
  251. package/src/authoring/object3d-document-persistence.ts +106 -0
  252. package/src/authoring/object3d-document-session-registry.ts +120 -0
  253. package/src/authoring/object3d-document-session.ts +1326 -0
  254. package/src/authoring/object3d-gesture-controller.ts +113 -0
  255. package/src/authoring/panel-authoring.ts +121 -0
  256. package/src/authoring/prefab-instance-inspector-section.tsx +237 -0
  257. package/src/authoring/prefab-instance-section-model.ts +55 -0
  258. package/src/authoring/project-authoring-session.ts +105 -0
  259. package/src/authoring/provenance.ts +99 -0
  260. package/src/authoring/quarks-particle-systems.ts +19 -0
  261. package/src/authoring/react-canvas-navigation.ts +255 -0
  262. package/src/authoring/react-design-canvas-style.ts +20 -0
  263. package/src/authoring/react-story-board.ts +937 -0
  264. package/src/authoring/selection-scope.ts +195 -0
  265. package/src/authoring/shell-document-ops.ts +169 -0
  266. package/src/authoring/shell-object3d-document-write-policy.ts +108 -0
  267. package/src/authoring/shell-viewport-policy.ts +48 -0
  268. package/src/authoring/source-object3d-authoring-adapter.ts +526 -0
  269. package/src/authoring/stories-scope.ts +35 -0
  270. package/src/authoring/story-board-chrome-fit.ts +107 -0
  271. package/src/authoring/story-board-presentation.ts +111 -0
  272. package/src/authoring/three-projection-core.ts +226 -0
  273. package/src/authoring/viewport-pick-context.ts +40 -0
  274. package/src/authoring/viewport-raycast.ts +240 -0
  275. package/src/authoring/viewport-tool-context.ts +73 -0
  276. package/src/authoring/world-canvas-viewport-state.ts +35 -0
  277. package/src/authoring/world-hidden-viewport.ts +152 -0
  278. package/src/authoring/world-pan-state.ts +198 -0
  279. package/src/authoring/world-session-state.ts +101 -0
  280. package/src/authoring/write-pipe.ts +173 -0
  281. package/src/availability-tick.ts +66 -0
  282. package/src/bitmap-label.ts +120 -0
  283. package/src/blender-tab-metrics.ts +161 -0
  284. package/src/board-open-actions.ts +20 -0
  285. package/src/boot-routing.ts +391 -0
  286. package/src/build-identity.ts +15 -0
  287. package/src/bytes-codec.ts +62 -0
  288. package/src/camera-authoring.ts +175 -0
  289. package/src/cancellation-reason.ts +58 -0
  290. package/src/canvas-preview-frames.ts +482 -0
  291. package/src/capture-camera-pose.ts +77 -0
  292. package/src/chrome-registry.ts +159 -0
  293. package/src/chrome-slot-registry.ts +91 -0
  294. package/src/collaboration-client.ts +264 -0
  295. package/src/collaboration-presence.ts +41 -0
  296. package/src/command-dispatch.ts +19 -0
  297. package/src/command-listener.ts +2743 -0
  298. package/src/command-registry.ts +70 -0
  299. package/src/component-board-registry.ts +205 -0
  300. package/src/component-states-registry.ts +200 -0
  301. package/src/components/AgentPresentationNotice.tsx +75 -0
  302. package/src/components/AlignToolbar.tsx +204 -0
  303. package/src/components/AppRoot.tsx +394 -0
  304. package/src/components/ApplicationMenus.tsx +415 -0
  305. package/src/components/AssetBrowser.tsx +2843 -0
  306. package/src/components/AssetEditorShell.tsx +216 -0
  307. package/src/components/AssetInspectorToolSection.tsx +124 -0
  308. package/src/components/BoardRulers.tsx +354 -0
  309. package/src/components/CameraInfo.tsx +81 -0
  310. package/src/components/CanvasSceneViewport.tsx +556 -0
  311. package/src/components/CapabilityCoverageSection.tsx +185 -0
  312. package/src/components/CenterDocuments.tsx +22 -0
  313. package/src/components/ChromeSlot.tsx +20 -0
  314. package/src/components/CodeView.tsx +470 -0
  315. package/src/components/CollaborationHeaderControl.css +74 -0
  316. package/src/components/CompactInspectorCard.tsx +190 -0
  317. package/src/components/CompactInspectorShell.tsx +39 -0
  318. package/src/components/ConsolePanel.css +55 -0
  319. package/src/components/ConsolePanel.tsx +270 -0
  320. package/src/components/DefaultEditorLayout.tsx +106 -0
  321. package/src/components/DocumentHeaderStrip.tsx +96 -0
  322. package/src/components/DocumentShelfRail.tsx +45 -0
  323. package/src/components/DocumentThumbnail.tsx +345 -0
  324. package/src/components/EditorLeaseGuard.tsx +372 -0
  325. package/src/components/ErrorBanner.tsx +123 -0
  326. package/src/components/FolderPreviewTile.tsx +159 -0
  327. package/src/components/GameHierarchy.tsx +3842 -0
  328. package/src/components/GameplaySessionTimeline.tsx +295 -0
  329. package/src/components/InspectionProjection.tsx +968 -0
  330. package/src/components/Inspector.tsx +233 -0
  331. package/src/components/InspectorCanvasPreview.tsx +35 -0
  332. package/src/components/InspectorFieldsSection.tsx +290 -0
  333. package/src/components/InspectorObjectPreview.tsx +57 -0
  334. package/src/components/InspectorStoriesSection.tsx +92 -0
  335. package/src/components/InspectorToolSection.tsx +96 -0
  336. package/src/components/InspectorTransformSection.tsx +245 -0
  337. package/src/components/LightExplorerPanel.tsx +433 -0
  338. package/src/components/MediaProperties.tsx +145 -0
  339. package/src/components/NonThreeAuthoringBootstrap.tsx +19 -0
  340. package/src/components/Object3DDocumentToolbar.css +100 -0
  341. package/src/components/Object3DDocumentToolbar.tsx +454 -0
  342. package/src/components/Object3DDocumentViewport.tsx +58 -0
  343. package/src/components/ProjectHeader.css +617 -0
  344. package/src/components/ProjectHeader.tsx +328 -0
  345. package/src/components/ProjectLayout.tsx +249 -0
  346. package/src/components/ReactCanvasControls.tsx +284 -0
  347. package/src/components/RootSelectionOverlay.tsx +3162 -0
  348. package/src/components/RootTextEditor.tsx +79 -0
  349. package/src/components/SaveStatus.tsx +70 -0
  350. package/src/components/StageHost.tsx +1954 -0
  351. package/src/components/StageOverlays.tsx +21 -0
  352. package/src/components/StartupErrorScreen.tsx +76 -0
  353. package/src/components/StartupLoadingScreen.tsx +65 -0
  354. package/src/components/StatsOverlay.tsx +78 -0
  355. package/src/components/SurfaceStateOverlay.tsx +24 -0
  356. package/src/components/ToolContributionSurfaces.tsx +65 -0
  357. package/src/components/ToolHost.tsx +370 -0
  358. package/src/components/ToolObject3DPreview.tsx +39 -0
  359. package/src/components/Toolbar.tsx +595 -0
  360. package/src/components/TransientHint.tsx +44 -0
  361. package/src/components/TransportStrip.tsx +174 -0
  362. package/src/components/VersionControlSection.tsx +470 -0
  363. package/src/components/VgaiLogo.css +83 -0
  364. package/src/components/VgaiLogo.tsx +35 -0
  365. package/src/components/ViewportControlsHint.tsx +60 -0
  366. package/src/components/ViewportFurniture.tsx +401 -0
  367. package/src/components/ViewportOverlay.tsx +145 -0
  368. package/src/components/ViewportOverlaysMenu.tsx +153 -0
  369. package/src/components/ViewportShadingMenu.tsx +300 -0
  370. package/src/components/ViewportViewMenu.tsx +101 -0
  371. package/src/components/WorkspaceDocumentSurface.tsx +204 -0
  372. package/src/components/WorkspaceUtilitySurface.tsx +22 -0
  373. package/src/components/WorktreeSwitcher.css +239 -0
  374. package/src/components/WorktreeSwitcher.tsx +890 -0
  375. package/src/components/account-documents.tsx +1162 -0
  376. package/src/components/asset-documents.tsx +812 -0
  377. package/src/components/asset-editor-persistence.ts +216 -0
  378. package/src/components/asset-selection-section.tsx +535 -0
  379. package/src/components/asset-thumbnails.tsx +312 -0
  380. package/src/components/asset-viewers/AudioViewer.tsx +201 -0
  381. package/src/components/asset-viewers/EntityModelDocument.tsx +122 -0
  382. package/src/components/asset-viewers/EnvironmentAssetDocument.tsx +440 -0
  383. package/src/components/asset-viewers/GenericJsonViewer.tsx +102 -0
  384. package/src/components/asset-viewers/ImageViewer.tsx +300 -0
  385. package/src/components/asset-viewers/JsonAssetDocument.tsx +93 -0
  386. package/src/components/asset-viewers/LiveModuleDocument.tsx +417 -0
  387. package/src/components/asset-viewers/LutAssetDocument.tsx +444 -0
  388. package/src/components/asset-viewers/ModelAssetDocument.tsx +105 -0
  389. package/src/components/asset-viewers/Object3DPreview.tsx +359 -0
  390. package/src/components/asset-viewers/OnlineAssetDetail.tsx +421 -0
  391. package/src/components/asset-viewers/PasteboardModuleDocument.tsx +67 -0
  392. package/src/components/asset-viewers/QuarksAssetDocument.tsx +526 -0
  393. package/src/components/asset-viewers/ShaderAssetDocument.tsx +743 -0
  394. package/src/components/asset-viewers/SourceAssetViewer.tsx +281 -0
  395. package/src/components/asset-viewers/SpritesheetSpriteView.tsx +102 -0
  396. package/src/components/asset-viewers/VideoViewer.tsx +101 -0
  397. package/src/components/asset-viewers/shader-source.ts +144 -0
  398. package/src/components/asset-workflow.css +731 -0
  399. package/src/components/board-guides.ts +150 -0
  400. package/src/components/compact-inspector.css +539 -0
  401. package/src/components/core-utilities.tsx +90 -0
  402. package/src/components/editor-notifications.css +40 -0
  403. package/src/components/engine-workspace.css +274 -0
  404. package/src/components/inspector-preview-section.tsx +238 -0
  405. package/src/components/inspector-property-grouping.ts +64 -0
  406. package/src/components/inspector-revert-label.ts +20 -0
  407. package/src/components/inspector-selection.ts +42 -0
  408. package/src/components/inspector-stories-gating.ts +171 -0
  409. package/src/components/inspector-transform-subject.ts +11 -0
  410. package/src/components/inspector-transform.ts +75 -0
  411. package/src/components/kind-documents.tsx +500 -0
  412. package/src/components/palette-action-publisher.tsx +140 -0
  413. package/src/components/primitives/DraftColorInput.tsx +74 -0
  414. package/src/components/product-shell.css +1829 -0
  415. package/src/components/project-tool-documents.tsx +410 -0
  416. package/src/components/scene-documents.tsx +223 -0
  417. package/src/components/stage-keyboard.tsx +37 -0
  418. package/src/components/stage-overlay-set.tsx +107 -0
  419. package/src/components/stage-presence-markers.ts +478 -0
  420. package/src/components/standard-viewport-dressing.ts +417 -0
  421. package/src/components/status-contributions.tsx +403 -0
  422. package/src/components/tool-documents.tsx +309 -0
  423. package/src/components/tool-schema-form.tsx +262 -0
  424. package/src/components/use-after-paint.ts +41 -0
  425. package/src/components/use-project-image-assets.ts +86 -0
  426. package/src/components/viewport-header-controls.css +90 -0
  427. package/src/components/viewport-surface-status.tsx +55 -0
  428. package/src/components/workspace-history.ts +32 -0
  429. package/src/components/workspace-static-panel-registry.tsx +152 -0
  430. package/src/components/workspace-surfaces.css +621 -0
  431. package/src/components/world-documents.tsx +586 -0
  432. package/src/components/world-overlay-gestures.ts +1694 -0
  433. package/src/composite-screenshot.ts +1641 -0
  434. package/src/console-sync.ts +131 -0
  435. package/src/constraint-helper.ts +338 -0
  436. package/src/content-entry-source-registry.ts +184 -0
  437. package/src/coverage/authoring-seam-evidence.ts +300 -0
  438. package/src/coverage/canvas-reveal.ts +192 -0
  439. package/src/coverage/design-time-surfaces.ts +101 -0
  440. package/src/coverage/live-seam-evidence.ts +11 -0
  441. package/src/coverage/ontology-invariants.ts +466 -0
  442. package/src/coverage/session-vitals.ts +501 -0
  443. package/src/coverage/system-seam-evidence.ts +72 -0
  444. package/src/crash-null-boundary.ts +36 -0
  445. package/src/creation-site-edit.ts +1479 -0
  446. package/src/creation-site-registry.ts +160 -0
  447. package/src/delegate-harness-registry.ts +143 -0
  448. package/src/document-context-registry.ts +177 -0
  449. package/src/document-open-registry.ts +200 -0
  450. package/src/document-preview-source.ts +20 -0
  451. package/src/document-renderer-session.ts +138 -0
  452. package/src/editor-api.ts +46 -0
  453. package/src/editor-chrome-capture.ts +139 -0
  454. package/src/editor-commands.ts +164 -0
  455. package/src/editor-console.ts +563 -0
  456. package/src/editor-current-view.ts +82 -0
  457. package/src/editor-document-probe.ts +881 -0
  458. package/src/editor-git-client.ts +115 -0
  459. package/src/editor-host-door.ts +462 -0
  460. package/src/editor-hotkeys.ts +842 -0
  461. package/src/editor-lease-view.ts +39 -0
  462. package/src/editor-lease.ts +415 -0
  463. package/src/editor-mode.ts +19 -0
  464. package/src/editor-notifications.ts +140 -0
  465. package/src/editor-presence.ts +563 -0
  466. package/src/editor-presentation-activity.ts +50 -0
  467. package/src/editor-presentation-notice.ts +42 -0
  468. package/src/editor-runtime.tsx +145 -0
  469. package/src/editor-server-response.ts +86 -0
  470. package/src/editor-session-attribution.ts +75 -0
  471. package/src/editor-session-mode.ts +54 -0
  472. package/src/editor-shell-store.ts +1304 -0
  473. package/src/editor-state-facets.ts +74 -0
  474. package/src/editor-styles.css +33 -0
  475. package/src/editor-view-presentation.ts +810 -0
  476. package/src/editor-viewport.ts +5302 -0
  477. package/src/entity-lod.ts +31 -0
  478. package/src/entity-object.ts +91 -0
  479. package/src/files/file-provider.ts +62 -0
  480. package/src/files/project-files.ts +264 -0
  481. package/src/finders/index.ts +136 -0
  482. package/src/finders/scenes-from-entrypoint-selection.ts +387 -0
  483. package/src/frame/bridge.tsx +1408 -0
  484. package/src/frame/product.ts +77 -0
  485. package/src/gameplay-dom-recording.ts +319 -0
  486. package/src/gameplay-export-state.ts +14 -0
  487. package/src/gameplay-replay.ts +417 -0
  488. package/src/gameplay-session-time.ts +9 -0
  489. package/src/gameplay-sessions.ts +204 -0
  490. package/src/harness-chat-types.ts +253 -0
  491. package/src/hierarchy-component-marks.ts +298 -0
  492. package/src/hierarchy-drop.ts +91 -0
  493. package/src/hierarchy-expansion-state.ts +80 -0
  494. package/src/hierarchy-header-slot.ts +52 -0
  495. package/src/hierarchy-internals.ts +197 -0
  496. package/src/hierarchy-kind-icon.ts +217 -0
  497. package/src/hierarchy-mark-reader.ts +73 -0
  498. package/src/hierarchy-menu-registry.ts +67 -0
  499. package/src/hierarchy-node-rows.ts +307 -0
  500. package/src/hierarchy-panel-view.ts +280 -0
  501. package/src/hierarchy-projection.ts +76 -0
  502. package/src/hierarchy-row-cache.ts +243 -0
  503. package/src/hierarchy-row-model.ts +308 -0
  504. package/src/hierarchy-rows.ts +11 -0
  505. package/src/hierarchy-walk.ts +86 -0
  506. package/src/history/editor-session.ts +25 -0
  507. package/src/history/history-commands.ts +147 -0
  508. package/src/history/history-delegate.ts +163 -0
  509. package/src/history/history-limit-notices.ts +43 -0
  510. package/src/history/history-service.ts +1173 -0
  511. package/src/history/persistence-coordinator.ts +35 -0
  512. package/src/history/project-file-history.ts +386 -0
  513. package/src/history/project-root-history-backends.ts +139 -0
  514. package/src/history/resource-registry.ts +209 -0
  515. package/src/history/snapshot-store.ts +103 -0
  516. package/src/history/source-history-backend.ts +546 -0
  517. package/src/history/types.ts +124 -0
  518. package/src/hmr-registration-group.ts +67 -0
  519. package/src/hmr-stable-react-context.ts +23 -0
  520. package/src/hotkeys.ts +188 -0
  521. package/src/inference-diagnostics.ts +69 -0
  522. package/src/initial-project.ts +80 -0
  523. package/src/inspection/active-subject.ts +578 -0
  524. package/src/inspection/active-surface.ts +142 -0
  525. package/src/inspection/compose.ts +1064 -0
  526. package/src/inspection/display.ts +170 -0
  527. package/src/inspection/document-subject.ts +109 -0
  528. package/src/inspection/game-subject.ts +85 -0
  529. package/src/inspection/model.ts +542 -0
  530. package/src/inspection/null-subject.ts +115 -0
  531. package/src/inspection/serialize.ts +357 -0
  532. package/src/inspection/use-active-inspection.ts +180 -0
  533. package/src/inspector-presentation.ts +201 -0
  534. package/src/inspector-section-registry.ts +221 -0
  535. package/src/instance-source-actions.ts +163 -0
  536. package/src/instanced-presentation.ts +164 -0
  537. package/src/js-heap.ts +71 -0
  538. package/src/key-actions.ts +91 -0
  539. package/src/keymap-presets.ts +401 -0
  540. package/src/layout-policy.ts +31 -0
  541. package/src/learn-links.ts +73 -0
  542. package/src/light-explorer-model.ts +134 -0
  543. package/src/live-canvas-frame.ts +55 -0
  544. package/src/live-document.ts +279 -0
  545. package/src/live-module-source.ts +230 -0
  546. package/src/live-session-registry.ts +220 -0
  547. package/src/live-transition.ts +633 -0
  548. package/src/manifest-project.ts +107 -0
  549. package/src/model-thumbnail.ts +523 -0
  550. package/src/native-selection-style.ts +202 -0
  551. package/src/object3d-document-write-policy.ts +137 -0
  552. package/src/packaged-runtime.ts +108 -0
  553. package/src/palettes/maya.palette.json +57 -0
  554. package/src/palettes/substance.palette.json +57 -0
  555. package/src/pasteboard-module.ts +81 -0
  556. package/src/performance-profiler.ts +367 -0
  557. package/src/performance-sources.ts +69 -0
  558. package/src/play-boot-phase.ts +145 -0
  559. package/src/presentation-surface.ts +248 -0
  560. package/src/project-adapter.ts +1143 -0
  561. package/src/project-asset-refresh.ts +26 -0
  562. package/src/project-declaration-refresh.ts +31 -0
  563. package/src/project-local-state.ts +118 -0
  564. package/src/project-manager.ts +243 -0
  565. package/src/project-module-changes.ts +163 -0
  566. package/src/project-module-split.ts +266 -0
  567. package/src/project-provenance.ts +115 -0
  568. package/src/project-ready.ts +42 -0
  569. package/src/project-session-reset.ts +67 -0
  570. package/src/project-shape.ts +68 -0
  571. package/src/project-tool-discovery.ts +66 -0
  572. package/src/project-tools.ts +107 -0
  573. package/src/project-work-types.ts +149 -0
  574. package/src/projection/three.ts +898 -0
  575. package/src/projection/types.ts +44 -0
  576. package/src/readiness.ts +113 -0
  577. package/src/reflection-probe-helper.ts +142 -0
  578. package/src/reported-play-state.ts +90 -0
  579. package/src/resolve-relative-specifier.ts +33 -0
  580. package/src/scene-document-plan.ts +315 -0
  581. package/src/scene-framing.ts +315 -0
  582. package/src/scene-live-open.ts +210 -0
  583. package/src/scene-view-fog.ts +89 -0
  584. package/src/scoped-game-css.ts +152 -0
  585. package/src/session-orphan-record.ts +193 -0
  586. package/src/session-tombstone.ts +126 -0
  587. package/src/settings/settings-provider.ts +82 -0
  588. package/src/settings-store.ts +345 -0
  589. package/src/shared-view-restore.ts +42 -0
  590. package/src/shell-store-door.ts +45 -0
  591. package/src/source-conflict.ts +122 -0
  592. package/src/spatial-handle-visuals.ts +332 -0
  593. package/src/stage-context.ts +341 -0
  594. package/src/stage-store-registry.ts +63 -0
  595. package/src/stale-chunk-recovery.ts +34 -0
  596. package/src/state-report-deferral.ts +73 -0
  597. package/src/storage/host-files-storage.ts +97 -0
  598. package/src/storage/http-storage.ts +174 -0
  599. package/src/storage/index.ts +75 -0
  600. package/src/storage/mem-storage.ts +149 -0
  601. package/src/storage/path-lock.ts +44 -0
  602. package/src/storage/paths.ts +26 -0
  603. package/src/storage/types.ts +116 -0
  604. package/src/stories/StoryComponentThumbnail.tsx +184 -0
  605. package/src/stories/StoryPreviewMount.tsx +306 -0
  606. package/src/stories/component-content-source.tsx +106 -0
  607. package/src/stories/component-states-source.ts +78 -0
  608. package/src/stories/compose-project-stories.ts +255 -0
  609. package/src/stories/pixi-story-model.ts +30 -0
  610. package/src/stories/prefabs-finder.ts +54 -0
  611. package/src/stories/prefabs-from-stories.ts +182 -0
  612. package/src/stories/project-story-discovery.ts +78 -0
  613. package/src/stories/project-story-regions.ts +24 -0
  614. package/src/stories/story-actions.ts +54 -0
  615. package/src/stories/story-capture-command.ts +124 -0
  616. package/src/stories/story-capture.ts +584 -0
  617. package/src/stories/story-declared-medium.ts +126 -0
  618. package/src/stories/story-discovery.ts +176 -0
  619. package/src/stories/story-document-openers.ts +36 -0
  620. package/src/stories/story-dom-runtime.ts +78 -0
  621. package/src/stories/story-grouping.ts +111 -0
  622. package/src/stories/story-lane.ts +88 -0
  623. package/src/stories/story-mount-turn.ts +27 -0
  624. package/src/stories/story-opener.ts +260 -0
  625. package/src/stories/story-pixi-preview.ts +408 -0
  626. package/src/stories/story-presentation.ts +215 -0
  627. package/src/stories/story-registry.ts +517 -0
  628. package/src/stories/story-three-preview.ts +807 -0
  629. package/src/stories/three-story-model.ts +96 -0
  630. package/src/story-three-preview-runtime.ts +56 -0
  631. package/src/surface-keyboard.ts +112 -0
  632. package/src/surface-state.ts +124 -0
  633. package/src/tab-bootstrap.js +628 -0
  634. package/src/tab-census.ts +198 -0
  635. package/src/tab-lifecycle-client.ts +196 -0
  636. package/src/theme-library.ts +844 -0
  637. package/src/theme-preference.ts +378 -0
  638. package/src/theme.css +3437 -0
  639. package/src/three-viewport/bone-selection-highlight.ts +119 -0
  640. package/src/three-viewport/camera-fit.ts +41 -0
  641. package/src/three-viewport/interactive-renderer.ts +132 -0
  642. package/src/three-viewport/selection-brackets.ts +310 -0
  643. package/src/three-viewport/selection-outline.ts +183 -0
  644. package/src/three-viewport/skeleton-helper.ts +61 -0
  645. package/src/three-viewport/source-color.ts +167 -0
  646. package/src/three-viewport/studio-environment.ts +96 -0
  647. package/src/three-viewport-presentation.ts +23 -0
  648. package/src/tool-contribution-play.ts +74 -0
  649. package/src/tool-loader.ts +1798 -0
  650. package/src/transform-mode-request.ts +57 -0
  651. package/src/transient-hint.ts +78 -0
  652. package/src/trigger-volume-helper.ts +116 -0
  653. package/src/ui-source/adapter-region-includes.ts +241 -0
  654. package/src/ui-source/ensure-import.ts +132 -0
  655. package/src/ui-source/file-region-resolver.ts +302 -0
  656. package/src/ui-source/inspect.ts +775 -0
  657. package/src/ui-source/oid-transform.ts +1845 -0
  658. package/src/ui-source/plan-csf-story.ts +196 -0
  659. package/src/ui-source/plan-extract-component.ts +421 -0
  660. package/src/ui-source/plan-fork-component.ts +621 -0
  661. package/src/ui-source/plan-named-style.ts +95 -0
  662. package/src/ui-source/plan-source-edit.ts +353 -0
  663. package/src/ui-source/r3f-contract-resolver.ts +327 -0
  664. package/src/ui-source/r3f-diagnostic-index.ts +165 -0
  665. package/src/ui-source/r3f-environment-binding.ts +133 -0
  666. package/src/ui-source/r3f-joint-binding.ts +251 -0
  667. package/src/ui-source/r3f-lod-binding.ts +91 -0
  668. package/src/ui-source/r3f-particle-binding.ts +235 -0
  669. package/src/ui-source/r3f-physics-binding.ts +483 -0
  670. package/src/ui-source/r3f-project-contracts.ts +444 -0
  671. package/src/ui-source/relative-import-specifier.ts +34 -0
  672. package/src/ui-source/reparent-guard.ts +544 -0
  673. package/src/ui-source/source-edit-request.ts +97 -0
  674. package/src/ui-source/source-write-backend.ts +618 -0
  675. package/src/ui-source/syntactic-prop-specs.ts +296 -0
  676. package/src/ui-source/tier-source-write-backend.ts +279 -0
  677. package/src/ui-source/ts-ast.ts +171 -0
  678. package/src/ui-source/utility-class-support.ts +169 -0
  679. package/src/ui-source/write-component-default.ts +133 -0
  680. package/src/ui-source/writer.ts +1989 -0
  681. package/src/viewport-activation-timings.ts +840 -0
  682. package/src/viewport-authoring-policy.ts +186 -0
  683. package/src/viewport-controls-hint.ts +57 -0
  684. package/src/viewport-door.ts +237 -0
  685. package/src/viewport-shading-boundary.ts +12 -0
  686. package/src/vite-error-surface.ts +141 -0
  687. package/src/wait-until.ts +37 -0
  688. package/src/workspace-areas.ts +156 -0
  689. package/src/workspace-aux-commands.ts +11 -0
  690. package/src/workspace-available-documents.ts +127 -0
  691. package/src/workspace-core-utilities.ts +31 -0
  692. package/src/workspace-document-ids.ts +59 -0
  693. package/src/workspace-document-registry.ts +612 -0
  694. package/src/workspace-document-restore.ts +145 -0
  695. package/src/workspace-host-commands.ts +141 -0
  696. package/src/workspace-persistence-gate.ts +40 -0
  697. package/src/workspace-play-utilities.ts +44 -0
  698. package/src/workspace-presets.ts +425 -0
  699. package/src/workspace-regions.ts +294 -0
  700. package/src/workspace-state-persistence.ts +536 -0
  701. package/src/workspace-static-panels.ts +73 -0
  702. package/src/workspace-status-registry.ts +121 -0
  703. package/src/workspace-style.ts +239 -0
  704. package/src/workspace-utility-commands.ts +74 -0
  705. package/src/workspace-utility-registry.ts +263 -0
  706. package/src/workspace-viewport-rect.ts +97 -0
  707. package/src/world-adoption.ts +115 -0
  708. package/src/world-document-routing.ts +104 -0
  709. package/vite-plugin-creation-site-write.ts +184 -0
  710. package/vite-plugin-creation-site.ts +80 -0
  711. package/vite-plugin-game-static.ts +303 -0
  712. package/vite-plugin-module-doorways.ts +336 -0
  713. package/vite-plugin-product-contributions.ts +197 -0
  714. package/vite-plugin-project-game-static.ts +125 -0
  715. package/vite-plugin-project-jsx-js.ts +65 -0
  716. package/vite-plugin-project-root-absolute-assets.ts +359 -0
  717. package/vite-plugin-shared-react.ts +420 -0
  718. package/vite-plugin-shared-three.ts +203 -0
  719. package/vite-plugin-ui-oid.ts +2106 -0
package/src/theme.css ADDED
@@ -0,0 +1,3437 @@
1
+ .vgai-editor-theme {
2
+ /* Legacy aliases. New code consumes the semantic variables above. */
3
+ --vgai-bg-0: var(--vgai-surface-shell);
4
+ --vgai-bg-1: var(--vgai-surface-panel);
5
+ --vgai-bg-2: var(--vgai-surface-chrome);
6
+ --vgai-bg-3: var(--vgai-surface-raised);
7
+ --vgai-bg-inset: var(--vgai-surface-inset);
8
+
9
+ /* Borders */
10
+ --vgai-border-1: var(--vgai-boundary-default);
11
+ --vgai-border-2: var(--vgai-boundary-strong);
12
+
13
+ /* Text */
14
+ --vgai-text-1: var(--vgai-content-primary);
15
+ --vgai-text-2: var(--vgai-content-muted);
16
+ --vgai-text-3: var(--vgai-content-dim);
17
+
18
+ /* Interior STICKY surfaces (--vgai-surface-sticky/-panel and their
19
+ * -backdrop-filter): emitted per-theme by editorThemeVariables() in
20
+ * theme.ts since §2.31 P2 — treatment themes use the translucent token +
21
+ * a sticky blur (soft occlusion), treatment-less themes the opaque
22
+ * token-over-shell stack. See stickySurfaceValue() there. */
23
+
24
+ /* Concrete spacing, dimension, type, and stacking variables are installed
25
+ * by editorThemeVariables() from theme.ts. Keeping their numbers out of CSS
26
+ * preserves one typed source of truth for class and inline-style consumers. */
27
+
28
+ /* Asset-kind category palette (asset-thumbnails.tsx, folder previews).
29
+ * These are stable per-kind data colors — category identity, not chrome
30
+ * paint (same doctrine as the color guard's visualization allowlist), so
31
+ * they stay literal instead of remapping through accent/semantic slots;
32
+ * both shipped editor themes are dark, and these mid-tone values read on
33
+ * each. Named --vgai-* so the migration guard's dangling-variable check
34
+ * owns them: the previous --asset-* spelling was silently deleted in a
35
+ * refactor and every glyph went invisible. */
36
+ --vgai-asset-icon-folder: #e8a740;
37
+ --vgai-asset-icon-model: #6bc5f7;
38
+ --vgai-asset-icon-prefab: #3b82f6;
39
+ --vgai-asset-icon-component: #9ca3af;
40
+ --vgai-asset-icon-audio: #34d399;
41
+ --vgai-asset-icon-image: #f472b6;
42
+ --vgai-asset-icon-video: #c084fc;
43
+ --vgai-asset-icon-json: #fbbf24;
44
+ --vgai-asset-icon-unknown: #6b7280;
45
+ --vgai-asset-icon-source: #fb923c;
46
+
47
+ /* Content-legibility text shadow (liquid glass, report §2.32): themes with
48
+ * a `textShadowOpacity` treatment axis emit a real shadow via this var;
49
+ * every other theme emits 'none', so this inherited declaration is inert
50
+ * for them. Near-white text + a concentrated local shadow keeps chrome legible
51
+ * when the surface itself is only a ≤15% white whisper over live scene
52
+ * content. */
53
+ text-shadow: var(--vgai-content-text-shadow, none);
54
+
55
+ /* THE BASELINE. Every descendant inherits the product's type from here and
56
+ * declares only what differs. Before this, the root minted the tokens but
57
+ * applied none of them, so each surface had to opt in per property, and the
58
+ * ones that forgot fell to the browser's serif at 16px (the agent notice,
59
+ * the command palette). Declared here rather than inherited from <body>: a
60
+ * game's own `body { font }` rule shares this document, and a declared
61
+ * value on the root is what keeps it out of editor chrome. */
62
+ font-family: var(--vgai-font-sans);
63
+ font-size: var(--vgai-font-base);
64
+ line-height: var(--vgai-leading-normal);
65
+ color: var(--vgai-content-primary);
66
+ }
67
+
68
+ /* Content-zone frost (§2.31 P2 owner amendment): text-dense interior zones
69
+ * get a LOCAL frosted-blur layer so text stays legible over open glass,
70
+ * while gutters/bezels stay clear and refractive. Both vars are emitted by
71
+ * editorThemeVariables() from the `contentFrostBlurPx` treatment axis —
72
+ * `transparent`/`none` for every theme without it (byte-identical paint,
73
+ * including the reduced-transparency degrade, which strips `treatment`).
74
+ * The parent card's refraction url() filter makes the card a backdrop root,
75
+ * so this blur samples the already-refracted backdrop — frost over the
76
+ * lensed scene, the intended composite. */
77
+ .vgai-content-frost {
78
+ background: var(--vgai-content-frost-bg, transparent);
79
+ backdrop-filter: var(--vgai-content-frost-filter, none);
80
+ -webkit-backdrop-filter: var(--vgai-content-frost-filter, none);
81
+ border-radius: var(--vgai-radius-md);
82
+ }
83
+
84
+ /* Adaptive foreground ink follows measured backdrop luminance. Compact
85
+ * navigation islands expose this as a local appearance flip. Larger Regular
86
+ * surfaces use the same foreground threshold, while retaining a shallow
87
+ * material correction independent of that ink decision. */
88
+ .vgai-editor-theme [data-vgai-adaptive-ink="bright"] {
89
+ --vgai-backdrop-dim: none;
90
+ --vgai-content-primary: var(--vgai-content-primary-on-bright);
91
+ --vgai-content-muted: var(--vgai-content-muted-on-bright);
92
+ --vgai-content-dim: var(--vgai-content-dim-on-bright);
93
+ --vgai-content-text-shadow: var(--vgai-content-text-shadow-on-bright);
94
+ --vgai-content-frost-bg: var(--vgai-content-frost-bg-on-bright);
95
+ --vgai-island-border-color: color-mix(
96
+ in srgb,
97
+ var(--vgai-content-primary-on-bright) 30%,
98
+ transparent
99
+ );
100
+ --vgai-text-1: var(--vgai-content-primary);
101
+ --vgai-text-2: var(--vgai-content-muted);
102
+ --vgai-text-3: var(--vgai-content-dim);
103
+ --dv-activegroup-visiblepanel-tab-color: var(--vgai-content-primary);
104
+ --dv-activegroup-hiddenpanel-tab-color: var(--vgai-content-muted);
105
+ --dv-inactivegroup-visiblepanel-tab-color: var(--vgai-content-muted);
106
+ --dv-inactivegroup-hiddenpanel-tab-color: var(--vgai-content-dim);
107
+ text-shadow: var(--vgai-content-text-shadow, none);
108
+ }
109
+
110
+ /* Complex authored UI is a deliberately non-adaptive backdrop. Regular
111
+ * panels over it use stable light ink and a strong dark frost rather than
112
+ * chasing local component colors as the interface changes beneath them. */
113
+ .vgai-editor-theme [data-vgai-panel-backdrop-policy="dark-frost"] {
114
+ --vgai-content-primary: #f4f8ff;
115
+ --vgai-content-muted: #c3cedd;
116
+ --vgai-content-dim: #94a2b5;
117
+ --vgai-content-text-shadow: 0 1px 3px rgb(0 0 0 / 72%);
118
+ --vgai-text-1: var(--vgai-content-primary);
119
+ --vgai-text-2: var(--vgai-content-muted);
120
+ --vgai-text-3: var(--vgai-content-dim);
121
+ --dv-activegroup-visiblepanel-tab-color: var(--vgai-content-primary);
122
+ --dv-activegroup-hiddenpanel-tab-color: var(--vgai-content-muted);
123
+ --dv-inactivegroup-visiblepanel-tab-color: var(--vgai-content-muted);
124
+ --dv-inactivegroup-hiddenpanel-tab-color: var(--vgai-content-dim);
125
+ }
126
+
127
+ /* Native controls otherwise fall back to the browser's unrelated UI font.
128
+ * Keep the reset scoped to the editor shell; component classes and inline
129
+ * styles can still opt into a different size or the monospace stack. */
130
+ .vgai-editor-shell button,
131
+ .vgai-editor-shell input,
132
+ .vgai-editor-shell select,
133
+ .vgai-editor-shell textarea {
134
+ font-family: inherit;
135
+ }
136
+
137
+ .vgai-editor-shell button {
138
+ font-size: inherit;
139
+ }
140
+
141
+ /* Visually hidden content remains available to assistive technology. */
142
+ .vgai-sr-only {
143
+ position: absolute;
144
+ width: 1px;
145
+ height: 1px;
146
+ padding: 0;
147
+ overflow: hidden;
148
+ clip: rect(0, 0, 0, 0);
149
+ white-space: nowrap;
150
+ border: 0;
151
+ }
152
+
153
+ .vgai-tooltip-anchor {
154
+ position: relative;
155
+ display: inline-flex;
156
+ }
157
+ .vgai-tooltip {
158
+ position: fixed;
159
+ z-index: var(--vgai-z-dropdown);
160
+ display: flex;
161
+ align-items: center;
162
+ gap: var(--vgai-space-4);
163
+ padding: var(--vgai-space-2) var(--vgai-space-4);
164
+ color: var(--vgai-content-primary);
165
+ background: var(--vgai-surface-inset);
166
+ border: var(--vgai-stroke-resting) solid var(--vgai-boundary-strong);
167
+ border-radius: var(--vgai-radius-md);
168
+ box-shadow: var(--vgai-shadow-sm);
169
+ font-size: var(--vgai-font-base);
170
+ white-space: nowrap;
171
+ pointer-events: none;
172
+ }
173
+ .vgai-tooltip kbd {
174
+ padding: 1px var(--vgai-space-3);
175
+ color: var(--vgai-content-primary);
176
+ background: var(--vgai-surface-raised);
177
+ border: var(--vgai-stroke-resting) solid var(--vgai-boundary-default);
178
+ border-radius: var(--vgai-radius-sm);
179
+ font-family: var(--vgai-font-mono);
180
+ font-size: var(--vgai-font-sm);
181
+ }
182
+ /* U6a (P6 glass-native chrome, inventory row "tooltip"): under islands
183
+ * chrome the tooltip is transient overlay chrome like `.vgai-menu` — the
184
+ * self-sufficient Regular material (`--vgai-glass-regular-bg` + its
185
+ * strong frost) statically paired with the `-on-bright` ink ramp, legible
186
+ * over ANY backdrop. Same re-declaration rationale as the `.vgai-menu`
187
+ * block below: descendants inherit the theme root's COMPUTED shadow, so the
188
+ * flipped var needs a fresh `text-shadow` declaration here. Bars chrome
189
+ * (graphite/classic, reduced-transparency/lite) never matches — the dark
190
+ * inset slab above stays byte-identical. The kbd chip re-tints from
191
+ * `currentColor` so it reads on either ink side. */
192
+ [data-vgai-chrome="islands"] .vgai-tooltip {
193
+ --vgai-content-primary: var(--vgai-content-primary-on-bright);
194
+ --vgai-content-muted: var(--vgai-content-muted-on-bright);
195
+ --vgai-content-dim: var(--vgai-content-dim-on-bright);
196
+ --vgai-content-text-shadow: var(--vgai-content-text-shadow-on-bright);
197
+ --vgai-text-1: var(--vgai-content-primary);
198
+ --vgai-text-2: var(--vgai-content-muted);
199
+ --vgai-text-3: var(--vgai-content-dim);
200
+ text-shadow: var(--vgai-content-text-shadow, none);
201
+ background: var(--vgai-glass-regular-bg, var(--vgai-surface-chrome));
202
+ border-color: var(--vgai-border-1);
203
+ backdrop-filter: var(--vgai-glass-regular-backdrop-filter, none);
204
+ -webkit-backdrop-filter: var(--vgai-glass-regular-backdrop-filter, none);
205
+ }
206
+ [data-vgai-chrome="islands"] .vgai-tooltip kbd {
207
+ color: inherit;
208
+ background: color-mix(in srgb, currentColor 10%, transparent);
209
+ border-color: color-mix(in srgb, currentColor 28%, transparent);
210
+ }
211
+
212
+ /* ---------------------------------------------------------------------- */
213
+ /* Canonical editor button family. All product button paint and states live */
214
+ /* here. */
215
+ /* ---------------------------------------------------------------------- */
216
+
217
+ .vgai-btn {
218
+ display: inline-flex;
219
+ /* Inline controls should keep their intrinsic size even when authored as
220
+ * direct children of a CSS grid. Grid's default align-items: stretch
221
+ * otherwise turns a compact action into a full-width slab. A caller that
222
+ * deliberately wants a wide button can still set width: 100%. */
223
+ width: max-content;
224
+ max-width: 100%;
225
+ align-items: center;
226
+ justify-content: center;
227
+ gap: var(--vgai-space-2);
228
+ font-family: var(--vgai-font-sans);
229
+ font-size: var(--vgai-font-md);
230
+ line-height: var(--vgai-leading-tight);
231
+ border-radius: var(--vgai-radius-sm);
232
+ cursor: pointer;
233
+ border: 1px solid transparent;
234
+ box-sizing: border-box;
235
+ transition:
236
+ background-color 0.12s var(--vgai-ease-standard),
237
+ border-color 0.12s var(--vgai-ease-standard),
238
+ color 0.12s var(--vgai-ease-standard),
239
+ filter 0.12s var(--vgai-ease-standard);
240
+ }
241
+
242
+ .vgai-btn[data-size="compact"] {
243
+ height: var(--vgai-control-compact-height);
244
+ padding-top: 0;
245
+ padding-bottom: 0;
246
+ font-size: var(--vgai-font-base);
247
+ }
248
+ .vgai-btn:not([data-size]),
249
+ .vgai-btn[data-size="default"] {
250
+ height: var(--vgai-control-default-height);
251
+ padding-top: 0;
252
+ padding-bottom: 0;
253
+ font-size: var(--vgai-font-base);
254
+ }
255
+ .vgai-btn[data-size="comfortable"] {
256
+ height: var(--vgai-control-comfortable-height);
257
+ padding-top: 0;
258
+ padding-bottom: 0;
259
+ font-size: var(--vgai-font-md);
260
+ }
261
+
262
+ .vgai-btn[data-variant="primary"] {
263
+ background: var(--vgai-accent);
264
+ color: var(--vgai-content-on-accent);
265
+ border-color: var(--vgai-accent);
266
+ padding: var(--vgai-space-3) var(--vgai-space-6);
267
+ font-weight: 600;
268
+ }
269
+ .vgai-btn[data-variant="primary"]:hover:not(:disabled) {
270
+ filter: brightness(1.08);
271
+ }
272
+ .vgai-btn[data-variant="primary"]:active:not(:disabled) {
273
+ filter: brightness(0.92);
274
+ }
275
+
276
+ /* A PUSHBUTTON — Blender's `wcol_regular` widget class. `--vgai-widget-regular`
277
+ resolves to `surface.raised` for any palette that does not name the class,
278
+ which is every palette but Blender's. */
279
+ .vgai-btn:not([data-variant]),
280
+ .vgai-btn[data-variant="secondary"] {
281
+ background: var(--vgai-widget-regular);
282
+ color: var(--vgai-content-primary);
283
+ border-color: var(--vgai-boundary-strong);
284
+ padding-inline: var(--vgai-space-4);
285
+ /* THE EMBOSS — Blender lays a 1 px translucent-black line under EVERY
286
+ widget, below its own outline. One rule, verified on five widget kinds
287
+ (pushbutton, text field, dropdown well, checkbox, list box) in
288
+ `properties-modifier.png` / `properties-object.png` /
289
+ `properties-data-edit.png`; the measurements and the alpha solve live on
290
+ `EditorTheme.color.widget.emboss`. It is a shadow, so it costs no layout
291
+ and a palette that names no emboss resolves the token to `none`. */
292
+ box-shadow: var(--vgai-widget-emboss-shadow);
293
+ }
294
+ .vgai-btn:not([data-variant]):hover:not(:disabled),
295
+ .vgai-btn[data-variant="secondary"]:hover:not(:disabled) {
296
+ /* Hover LIFTS the widget (Blender lifts a widget by a step); the bar colour it
297
+ used to take sank it below its own resting fill under an opaque skin. */
298
+ background: var(--vgai-widget-regular-hover);
299
+ border-color: var(--vgai-boundary-strong);
300
+ }
301
+
302
+ /* A MENU BUTTON — Blender's `wcol_menu`, a dark well, not a pushbutton: a
303
+ dropdown TRIGGER is a different widget class from a button that does a
304
+ thing, and painting both from one surface is what made `Object Mode v`
305
+ read pushbutton-grey under the Blender skin (measured #272727 against our
306
+ #545454). The discriminator is the trigger's own ARIA — a button that opens
307
+ a menu or a listbox IS a menu button — so no call site grows a marker
308
+ attribute. Scoped to the two button-shaped variants only: `ghost` menu
309
+ words (the viewport header's View/Select/Add, the app menus' own
310
+ `.vgai-menu-trigger`) carry no chrome in Blender either, and must not
311
+ acquire a well here.
312
+
313
+ `.vgai-field-trigger` is deliberately NOT in this list: it is a LAYOUT
314
+ class (full width, mono, left-aligned) and two of its three users are
315
+ genuine select-like triggers while the third
316
+ (`@vgai/dom/react-inspector-section.tsx`'s "No gradient — click to add")
317
+ is a plain action that opens nothing. Paint follows what the control DOES,
318
+ which the ARIA already states.
319
+
320
+ `dialog` is in the list because a value-labelled trigger that opens an
321
+ anchored SETTINGS popover (`Studio v`, the Asset Lab preview environment)
322
+ is the same widget as one that opens a menu — Blender draws both as one
323
+ well — and its popup is a form, so `aria-haspopup="dialog"` is the honest
324
+ ARIA and must not be bent to win paint. The one other `dialog` trigger in
325
+ the estate (the conversation tray) is `ghost`, which this rule does not
326
+ reach. */
327
+ .vgai-btn:is(:not([data-variant]), [data-variant="secondary"]):is(
328
+ [aria-haspopup="menu"],
329
+ [aria-haspopup="listbox"],
330
+ [aria-haspopup="dialog"]
331
+ ) {
332
+ background: var(--vgai-widget-menu);
333
+ }
334
+ .vgai-btn:is(:not([data-variant]), [data-variant="secondary"]):is(
335
+ [aria-haspopup="menu"],
336
+ [aria-haspopup="listbox"],
337
+ [aria-haspopup="dialog"]
338
+ ):hover:not(:disabled) {
339
+ background: var(--vgai-widget-menu-hover);
340
+ }
341
+ .vgai-btn:not([data-variant]):active:not(:disabled),
342
+ .vgai-btn[data-variant="secondary"]:active:not(:disabled) {
343
+ background: var(--vgai-surface-inset);
344
+ }
345
+
346
+ .vgai-btn[data-variant="outline"] {
347
+ background: transparent;
348
+ color: var(--vgai-content-primary);
349
+ border-color: var(--vgai-boundary-default);
350
+ padding-inline: var(--vgai-space-4);
351
+ }
352
+ .vgai-btn[data-variant="outline"]:hover:not(:disabled) {
353
+ background: var(--vgai-neutral-hover);
354
+ border-color: var(--vgai-boundary-strong);
355
+ }
356
+ .vgai-btn[data-variant="outline"]:active:not(:disabled) {
357
+ background: var(--vgai-neutral-active);
358
+ }
359
+
360
+ .vgai-btn[data-variant="ghost"] {
361
+ background: transparent;
362
+ color: var(--vgai-content-muted);
363
+ border-color: transparent;
364
+ padding-inline: var(--vgai-space-3);
365
+ }
366
+ .vgai-btn[data-variant="ghost"]:hover:not(:disabled) {
367
+ color: var(--vgai-content-primary);
368
+ background: var(--vgai-neutral-hover);
369
+ }
370
+ .vgai-btn[data-variant="ghost"]:active:not(:disabled) {
371
+ background: var(--vgai-neutral-active);
372
+ }
373
+
374
+ .vgai-btn[data-variant="danger"] {
375
+ color: var(--vgai-content-primary);
376
+ background: var(--vgai-danger-muted);
377
+ border-color: var(--vgai-danger);
378
+ padding-inline: var(--vgai-space-4);
379
+ font-weight: 600;
380
+ }
381
+ .vgai-btn[data-variant="danger"]:hover:not(:disabled) {
382
+ filter: brightness(1.12);
383
+ }
384
+ .vgai-btn[data-variant="danger"]:active:not(:disabled) {
385
+ filter: brightness(0.9);
386
+ }
387
+
388
+ .vgai-btn[data-variant="success"] {
389
+ color: var(--vgai-content-on-accent);
390
+ background: var(--vgai-success);
391
+ border-color: var(--vgai-success);
392
+ padding-inline: var(--vgai-space-4);
393
+ font-weight: 600;
394
+ }
395
+ .vgai-btn[data-variant="success"]:hover:not(:disabled) {
396
+ filter: brightness(1.1);
397
+ }
398
+ .vgai-btn[data-variant="success"]:active:not(:disabled) {
399
+ filter: brightness(0.92);
400
+ }
401
+
402
+ .vgai-btn[aria-pressed="true"] {
403
+ /* The ink PAIRS WITH THE FILL, and the fill differs per skin: white on
404
+ Blender's solid widget blue, the ordinary ink on the translucent accent
405
+ wash every other skin uses. `theme.ts`'s `selectionInkValue` derives it —
406
+ hardcoding on-accent here painted near-black on a dark wash and made a
407
+ pressed toggle unreadable in every skin but Blender. */
408
+ color: var(--vgai-content-on-selection, var(--vgai-content-primary));
409
+ background: var(--vgai-selection-bg);
410
+ border-color: var(--vgai-selection-border);
411
+ }
412
+ .vgai-btn[aria-busy="true"] {
413
+ cursor: progress;
414
+ }
415
+
416
+ .vgai-btn.vgai-sprite-frame-option {
417
+ width: 100%;
418
+ min-width: 0;
419
+ height: 72px;
420
+ flex-direction: column;
421
+ gap: var(--vgai-space-1);
422
+ padding: var(--vgai-space-1);
423
+ background: var(--vgai-surface-inset);
424
+ border-color: var(--vgai-boundary-default);
425
+ }
426
+
427
+ .vgai-btn.vgai-sprite-frame-option[aria-pressed="true"] {
428
+ background: var(--vgai-selection-bg);
429
+ border-color: var(--vgai-selection-border);
430
+ }
431
+
432
+ .vgai-btn[data-icon="true"] {
433
+ width: var(--vgai-control-default-height);
434
+ padding: 0;
435
+ }
436
+ .vgai-btn[data-icon="true"][data-size="compact"] {
437
+ width: var(--vgai-control-compact-height);
438
+ }
439
+ .vgai-btn[data-icon="true"][data-size="comfortable"] {
440
+ width: var(--vgai-control-comfortable-height);
441
+ }
442
+
443
+ /* A labelled icon button is the same canonical toolbar control with a
444
+ vertical content arrangement. Toolbars choose whether its pressed state is
445
+ independent or part of one exclusive selection; product surfaces don't
446
+ author their own icon/label metrics. */
447
+ .vgai-btn[data-icon="true"][data-label-placement="below"] {
448
+ width: 68px;
449
+ min-width: 68px;
450
+ height: 42px;
451
+ flex-direction: column;
452
+ gap: 3px;
453
+ padding: 4px 8px 3px;
454
+ }
455
+ .vgai-icon-button-label {
456
+ max-width: 100%;
457
+ min-width: 0;
458
+ overflow: hidden;
459
+ font-size: var(--vgai-font-xs);
460
+ line-height: var(--vgai-leading-tight);
461
+ text-overflow: ellipsis;
462
+ white-space: nowrap;
463
+ }
464
+
465
+ .vgai-btn:disabled,
466
+ .vgai-btn[aria-disabled="true"] {
467
+ opacity: 0.4;
468
+ cursor: not-allowed;
469
+ filter: none;
470
+ }
471
+
472
+ .vgai-btn:focus-visible {
473
+ outline: var(--vgai-focus-ring);
474
+ outline-offset: 1px;
475
+ }
476
+
477
+ .vgai-machine-list-entry {
478
+ display: block;
479
+ width: 100%;
480
+ height: auto;
481
+ justify-content: flex-start;
482
+ border-left: var(--vgai-stroke-active) solid transparent;
483
+ padding: var(--vgai-space-3) var(--vgai-space-4);
484
+ text-align: left;
485
+ }
486
+ /* Selected rows are a thin state overlay on the panel's one content floor,
487
+ never another blur-bearing layer. */
488
+ .vgai-machine-list-entry[data-selected="true"] {
489
+ border-left-color: var(--vgai-selection-indicator);
490
+ background: var(--vgai-selection-bg);
491
+ }
492
+ .vgai-performance-overlay {
493
+ pointer-events: auto;
494
+ display: grid;
495
+ grid-template-columns: repeat(5, auto);
496
+ width: auto;
497
+ height: auto;
498
+ gap: var(--vgai-space-5);
499
+ border-color: var(--vgai-boundary-strong);
500
+ background: var(--vgai-surface-overlay);
501
+ color: var(--vgai-content-primary);
502
+ padding: var(--vgai-space-3) var(--vgai-space-4);
503
+ font-family: var(--vgai-font-mono);
504
+ font-size: var(--vgai-font-sm);
505
+ }
506
+ .vgai-performance-overlay[data-enabled="true"] {
507
+ border-color: var(--vgai-accent);
508
+ }
509
+ /* Viewport HUD overlays (P6-U6, owner taste decision 3 — "HUD overlays →
510
+ real islands"): the shared base paint for the stats / network /
511
+ tile-palette HUD whispers, byte-identical to their former inline recipes
512
+ (overlay surface, radius-md, per-site border/shadow variants). Under
513
+ islands chrome each of these elements also carries `vgai-chrome-island`
514
+ (+ `vgai-glass-island` for the refraction upgrade), whose higher-
515
+ specificity material rules override this base — that is the promotion. */
516
+ .vgai-hud-overlay {
517
+ background: var(--vgai-surface-overlay);
518
+ border-radius: var(--vgai-radius-md);
519
+ }
520
+ .vgai-hud-overlay[data-hud-border="strong"] {
521
+ border: var(--vgai-stroke-resting) solid var(--vgai-boundary-strong);
522
+ }
523
+ .vgai-hud-overlay[data-hud-border="default"] {
524
+ border: var(--vgai-stroke-resting) solid var(--vgai-boundary-default);
525
+ }
526
+ .vgai-hud-overlay[data-hud-shadow="true"] {
527
+ box-shadow: var(--vgai-shadow-sm);
528
+ }
529
+ .vgai-field-trigger {
530
+ width: 100%;
531
+ justify-content: space-between;
532
+ overflow: hidden;
533
+ font-family: var(--vgai-font-mono);
534
+ text-align: left;
535
+ }
536
+ .vgai-inspector-group-trigger {
537
+ width: 100%;
538
+ height: auto;
539
+ justify-content: flex-start;
540
+ padding: var(--vgai-space-4);
541
+ font-weight: 600;
542
+ text-align: left;
543
+ text-transform: uppercase;
544
+ letter-spacing: 0.4px;
545
+ }
546
+ .vgai-hierarchy-more {
547
+ width: 100%;
548
+ height: var(--vgai-tree-row-height);
549
+ justify-content: flex-start;
550
+ font-style: italic;
551
+ text-align: left;
552
+ }
553
+ /* The tree's disclosure cell. Sized on the ICON axis, not the type axis: the
554
+ caret is a pictogram, and the icon rungs default to the type scale's values
555
+ so no skin that declares none moves. Blender's chevron measures 4.5x9 CSS
556
+ collapsed and 8.5x5 expanded on the native 2x `outliner.png`. */
557
+ .vgai-btn.vgai-tree-caret {
558
+ /* ONE INDENT CELL, and no inline padding — a button's `padding-inline` is
559
+ what made the expander's cell 19 px against the leaf spacer's 14, so a
560
+ child row's type glyph sat 15 px right of its parent's where the tree's
561
+ own step is 20. Blender's chevron and type glyph each own exactly one
562
+ indent cell (`outliner.png`: chevron centres 30.25/50.25, glyph centres
563
+ one cell right of each), which is what this width says. */
564
+ width: var(--vgai-tree-indent);
565
+ height: 18px;
566
+ flex: 0 0 var(--vgai-tree-indent);
567
+ padding-inline: 0;
568
+ font-size: var(--vgai-icon-xs);
569
+ }
570
+ .vgai-chat-icon {
571
+ width: var(--vgai-control-default-height);
572
+ padding-inline: 0;
573
+ }
574
+ .vgai-chat-session-row {
575
+ width: 100%;
576
+ height: auto;
577
+ justify-content: flex-start;
578
+ padding: var(--vgai-space-4);
579
+ text-align: left;
580
+ }
581
+ .vgai-chat-session-row[data-selected="true"] {
582
+ background: var(--vgai-surface-raised);
583
+ }
584
+ .vgai-chat-harness-trigger {
585
+ max-width: 190px;
586
+ overflow: hidden;
587
+ white-space: nowrap;
588
+ }
589
+ .vgai-chat-header-trigger {
590
+ min-width: 0;
591
+ flex: 1;
592
+ justify-content: flex-start;
593
+ overflow: hidden;
594
+ text-align: left;
595
+ }
596
+ /* ---------------------------------------------------------------------- */
597
+ /* .vgai-input / .vgai-select / .vgai-checkbox — shared form-control */
598
+ /* classes. */
599
+ /* ---------------------------------------------------------------------- */
600
+
601
+ .vgai-input {
602
+ background: var(--vgai-input-surface, var(--vgai-bg-inset));
603
+ color: var(--vgai-text-1);
604
+ border: 1px solid var(--vgai-input-border, var(--vgai-border-1));
605
+ border-radius: var(--vgai-radius-sm);
606
+ box-shadow: var(--vgai-input-shadow, none);
607
+ padding: var(--vgai-space-1) var(--vgai-space-3);
608
+ font-size: var(--vgai-font-base);
609
+ font-family: var(--vgai-font-sans);
610
+ outline: none;
611
+ min-width: 0;
612
+ min-height: var(--vgai-control-default-height);
613
+ box-sizing: border-box;
614
+ }
615
+ /* A PLACEHOLDER IS A PALETTE INK, and until this rule existed it was a
616
+ BROWSER's. No `::placeholder` declaration existed anywhere in the editor's
617
+ CSS, so Chrome's UA default decided one of our inks — measured live at a
618
+ fixed rgb(117,117,117), identical for fields inked 229, 100, red and black,
619
+ which is what says it is a constant of the browser's and not a composite of
620
+ ours. Blender's own is #5e5e5e (94), measured on the Outliner's `Search` in
621
+ `modeling-object-none.png` (x 3100..3260, y 60..96, ceiling 94) — below its
622
+ dim, which is where a placeholder belongs. `content.placeholder` falls back
623
+ to `content.dim` in the emitter; see theme.ts for why the fallback is the
624
+ palette's ink rather than a transcription of the browser's. */
625
+ .vgai-input::placeholder {
626
+ color: var(--vgai-content-placeholder);
627
+ /* Chrome's UA rule carries no opacity, but Firefox's does (0.54) and would
628
+ halve the declared ink. Stated so the token means the same on both. */
629
+ opacity: 1;
630
+ }
631
+ /* A single-line field is exactly control height, like `.vgai-btn`; padding
632
+ used to add to it, so a 20 px density still drew a 22 px field. */
633
+ input.vgai-input,
634
+ select.vgai-input {
635
+ height: var(--vgai-control-default-height);
636
+ padding-block: 0;
637
+ }
638
+ .vgai-input:hover:not(:disabled) {
639
+ border-color: var(--vgai-border-2);
640
+ }
641
+ .vgai-input:focus-visible {
642
+ border-color: var(--vgai-accent);
643
+ outline: var(--vgai-focus-ring);
644
+ outline-offset: -1px;
645
+ }
646
+ .vgai-input[aria-invalid="true"] {
647
+ border-color: var(--vgai-danger);
648
+ }
649
+ .vgai-input[data-tone="danger"] {
650
+ color: var(--vgai-danger);
651
+ }
652
+ .vgai-input[data-state="editing"] {
653
+ border-color: var(--vgai-accent);
654
+ }
655
+ .vgai-input[data-emphasis="strong"] {
656
+ font-size: var(--vgai-font-md);
657
+ font-weight: 600;
658
+ }
659
+ .vgai-input[data-emphasis="semibold"] {
660
+ font-weight: 600;
661
+ }
662
+ .vgai-input[data-monospace="true"] {
663
+ font-family: var(--vgai-font-mono);
664
+ }
665
+ .vgai-input[data-density="compact"],
666
+ .vgai-select[data-density="compact"] {
667
+ min-height: var(--vgai-control-compact-height);
668
+ font-size: var(--vgai-font-sm);
669
+ }
670
+ .vgai-input[data-density="comfortable"],
671
+ .vgai-select[data-density="comfortable"] {
672
+ min-height: var(--vgai-control-comfortable-height);
673
+ font-size: var(--vgai-font-md);
674
+ }
675
+ .vgai-input[data-density="compact-tall"],
676
+ .vgai-select[data-density="compact-tall"] {
677
+ min-height: var(--vgai-control-comfortable-height);
678
+ font-size: var(--vgai-font-sm);
679
+ }
680
+ .vgai-input[data-leading-icon="true"] {
681
+ padding-left: calc(var(--vgai-space-12) + 1px);
682
+ }
683
+ .vgai-input[data-variant="entity-name"] {
684
+ min-height: var(--vgai-control-comfortable-height);
685
+ padding: var(--vgai-space-2) var(--vgai-space-3);
686
+ font-size: var(--vgai-font-lg);
687
+ font-weight: 600;
688
+ }
689
+ .vgai-input[data-variant="tree-edit"] {
690
+ min-height: var(--vgai-control-compact-height);
691
+ padding: 0 var(--vgai-space-2);
692
+ border-color: var(--vgai-accent);
693
+ font-size: var(--vgai-font-lg);
694
+ outline: none;
695
+ }
696
+ .vgai-input[data-variant="prominent"] {
697
+ min-height: var(--vgai-command-bar-height);
698
+ padding: var(--vgai-space-5) calc(var(--vgai-space-6) + var(--vgai-space-1));
699
+ border: 0;
700
+ border-bottom: var(--vgai-stroke-resting) solid var(--vgai-boundary-strong);
701
+ border-radius: 0;
702
+ font-size: var(--vgai-font-xl);
703
+ }
704
+ .vgai-input[data-variant="prominent"]:focus-visible {
705
+ outline-offset: -2px;
706
+ }
707
+ .vgai-input[data-variant="seamless"] {
708
+ padding: var(--vgai-space-4);
709
+ color: var(--vgai-content-primary);
710
+ background: transparent;
711
+ border: 0;
712
+ border-radius: 0;
713
+ }
714
+ .vgai-input[data-dynamic="true"],
715
+ .vgai-select[data-dynamic="true"] {
716
+ color: var(--vgai-dynamic);
717
+ background-color: var(--vgai-dynamic-muted);
718
+ border-color: var(--vgai-dynamic);
719
+ }
720
+ .vgai-input[data-mixed="true"],
721
+ .vgai-select[data-mixed="true"] {
722
+ color: var(--vgai-content-dim);
723
+ font-style: italic;
724
+ }
725
+ .vgai-input:disabled {
726
+ opacity: 0.5;
727
+ cursor: not-allowed;
728
+ }
729
+
730
+ .vgai-select {
731
+ appearance: none;
732
+ -webkit-appearance: none;
733
+ background-color: var(--vgai-input-surface, var(--vgai-bg-inset));
734
+ background-image: var(--vgai-select-chevron);
735
+ background-repeat: no-repeat;
736
+ background-position: right 6px center;
737
+ background-size: 8px 5px;
738
+ color: var(--vgai-text-1);
739
+ border: 1px solid var(--vgai-input-border, var(--vgai-border-1));
740
+ border-radius: var(--vgai-radius-sm);
741
+ box-shadow: var(--vgai-input-shadow, none);
742
+ padding: var(--vgai-space-1) var(--vgai-space-6) var(--vgai-space-1) var(--vgai-space-3);
743
+ font-size: var(--vgai-font-base);
744
+ font-family: var(--vgai-font-sans);
745
+ outline: none;
746
+ min-height: var(--vgai-control-default-height);
747
+ box-sizing: border-box;
748
+ cursor: pointer;
749
+ }
750
+ .vgai-select:hover:not(:disabled) {
751
+ border-color: var(--vgai-border-2);
752
+ }
753
+ .vgai-select:focus-visible {
754
+ border-color: var(--vgai-accent);
755
+ outline: var(--vgai-focus-ring);
756
+ outline-offset: -1px;
757
+ }
758
+ .vgai-select:disabled {
759
+ opacity: 0.5;
760
+ cursor: not-allowed;
761
+ }
762
+ .vgai-select option {
763
+ background: var(--vgai-bg-2);
764
+ color: var(--vgai-text-1);
765
+ }
766
+
767
+ .vgai-textarea {
768
+ display: block;
769
+ width: 100%;
770
+ min-height: calc(var(--vgai-control-default-height) * 2);
771
+ resize: vertical;
772
+ line-height: var(--vgai-leading-normal);
773
+ }
774
+ .vgai-textarea[data-variant="code"] {
775
+ padding: var(--vgai-space-5);
776
+ font-family: var(--vgai-font-mono);
777
+ font-size: var(--vgai-font-base);
778
+ line-height: var(--vgai-leading-relaxed);
779
+ }
780
+ .vgai-textarea[data-variant="inline-editor"] {
781
+ padding: var(--vgai-space-1);
782
+ color: var(--vgai-content-primary);
783
+ background: var(--vgai-input-surface, var(--vgai-surface-inset));
784
+ border: var(--vgai-stroke-active) solid var(--vgai-accent);
785
+ font: inherit;
786
+ font-size: var(--vgai-font-lg);
787
+ line-height: 1.3;
788
+ outline: none;
789
+ }
790
+ /* ---------------------------------------------------------------------- */
791
+ /* Public contribution recipes. These are deliberately ordinary semantic */
792
+ /* HTML classes, not an extension UI framework: package contributions keep */
793
+ /* familiar React/HTML while the host owns spacing, paint, and responsive */
794
+ /* behavior. */
795
+ /* ---------------------------------------------------------------------- */
796
+
797
+ .vgai-tool-page {
798
+ width: 100%;
799
+ max-width: 960px;
800
+ min-height: 100%;
801
+ margin: 0 auto;
802
+ padding: var(--vgai-space-6);
803
+ color: var(--vgai-content-primary);
804
+ box-sizing: border-box;
805
+ }
806
+ .vgai-tool-header {
807
+ max-width: 720px;
808
+ margin-bottom: var(--vgai-space-5);
809
+ }
810
+ .vgai-tool-eyebrow {
811
+ margin-bottom: var(--vgai-space-2);
812
+ color: var(--vgai-content-dim);
813
+ font-size: var(--vgai-font-sm);
814
+ font-weight: 600;
815
+ letter-spacing: 0.06em;
816
+ text-transform: uppercase;
817
+ }
818
+ .vgai-tool-title {
819
+ margin: 0 0 var(--vgai-space-2);
820
+ font-size: var(--vgai-font-xl);
821
+ }
822
+ .vgai-tool-description {
823
+ margin: 0;
824
+ color: var(--vgai-content-muted);
825
+ line-height: var(--vgai-leading-relaxed);
826
+ }
827
+ .vgai-tool-card {
828
+ padding: var(--vgai-space-5);
829
+ border: var(--vgai-stroke-resting) solid var(--vgai-boundary-default);
830
+ border-radius: var(--vgai-radius-md);
831
+ background: var(--vgai-surface-panel);
832
+ }
833
+ .vgai-tool-form {
834
+ display: grid;
835
+ max-width: 720px;
836
+ gap: var(--vgai-space-4);
837
+ }
838
+ .vgai-tool-field {
839
+ display: grid;
840
+ min-width: 0;
841
+ gap: var(--vgai-space-2);
842
+ }
843
+ .vgai-tool-field > .vgai-input,
844
+ .vgai-tool-field > .vgai-select {
845
+ width: 100%;
846
+ }
847
+ .vgai-tool-field-label {
848
+ color: var(--vgai-content-primary);
849
+ font-weight: 500;
850
+ }
851
+ .vgai-tool-help {
852
+ color: var(--vgai-content-dim);
853
+ font-size: var(--vgai-font-sm);
854
+ line-height: var(--vgai-leading-normal);
855
+ }
856
+ .vgai-tool-advanced {
857
+ padding: var(--vgai-space-3);
858
+ border: var(--vgai-stroke-resting) solid var(--vgai-boundary-default);
859
+ border-radius: var(--vgai-radius-sm);
860
+ background: var(--vgai-surface-inset);
861
+ }
862
+ .vgai-tool-advanced > summary {
863
+ color: var(--vgai-content-muted);
864
+ cursor: pointer;
865
+ font-weight: 500;
866
+ }
867
+ .vgai-tool-advanced[open] > summary {
868
+ margin-bottom: var(--vgai-space-3);
869
+ color: var(--vgai-content-primary);
870
+ }
871
+ .vgai-tool-actions {
872
+ display: flex;
873
+ flex-wrap: wrap;
874
+ align-items: center;
875
+ gap: var(--vgai-space-3);
876
+ padding-top: var(--vgai-space-1);
877
+ }
878
+ .vgai-cost-disclosure {
879
+ display: grid;
880
+ gap: var(--vgai-space-1);
881
+ padding: var(--vgai-space-3) var(--vgai-space-4);
882
+ border: var(--vgai-stroke-resting) solid var(--vgai-boundary-default);
883
+ border-left: var(--vgai-stroke-active) solid var(--vgai-accent);
884
+ border-radius: var(--vgai-radius-sm);
885
+ background: var(--vgai-surface-inset);
886
+ }
887
+ .vgai-cost-disclosure[data-route="mock"] {
888
+ border-left-color: var(--vgai-success);
889
+ }
890
+ .vgai-cost-disclosure[data-route="managed"] {
891
+ border-left-color: var(--vgai-accent);
892
+ }
893
+ .vgai-cost-disclosure[data-route="direct"] {
894
+ border-left-color: var(--vgai-warn);
895
+ }
896
+ .vgai-cost-disclosure-title {
897
+ font-weight: 600;
898
+ }
899
+ .vgai-cost-disclosure-detail {
900
+ color: var(--vgai-content-muted);
901
+ font-size: var(--vgai-font-sm);
902
+ }
903
+ .vgai-tool-confirmation {
904
+ display: grid;
905
+ gap: var(--vgai-space-3);
906
+ padding: var(--vgai-space-4);
907
+ border: var(--vgai-stroke-resting) solid var(--vgai-warn);
908
+ border-radius: var(--vgai-radius-sm);
909
+ background: var(--vgai-warn-muted);
910
+ }
911
+ .vgai-tool-status {
912
+ max-width: 720px;
913
+ margin-top: var(--vgai-space-4);
914
+ padding: var(--vgai-space-3) var(--vgai-space-4);
915
+ border-radius: var(--vgai-radius-sm);
916
+ color: var(--vgai-content-primary);
917
+ background: var(--vgai-surface-raised);
918
+ }
919
+ .vgai-tool-status[data-tone="error"] {
920
+ color: var(--vgai-danger);
921
+ background: var(--vgai-danger-faint);
922
+ }
923
+ .vgai-tool-status[data-tone="success"] {
924
+ color: var(--vgai-success);
925
+ background: var(--vgai-success-muted);
926
+ }
927
+
928
+ .vgai-generation-job-meta {
929
+ color: var(--vgai-content-muted);
930
+ font-size: var(--vgai-font-sm);
931
+ }
932
+ .vgai-generation-document {
933
+ display: grid;
934
+ width: 100%;
935
+ max-width: 1120px;
936
+ min-height: 100%;
937
+ margin: 0 auto;
938
+ padding: var(--vgai-space-6);
939
+ gap: var(--vgai-space-4);
940
+ color: var(--vgai-content-primary);
941
+ box-sizing: border-box;
942
+ }
943
+ .vgai-generation-document-header {
944
+ display: flex;
945
+ align-items: flex-start;
946
+ justify-content: space-between;
947
+ gap: var(--vgai-space-4);
948
+ }
949
+ .vgai-generation-result {
950
+ min-height: 280px;
951
+ padding: var(--vgai-space-4);
952
+ overflow: auto;
953
+ border: var(--vgai-stroke-resting) solid var(--vgai-boundary-default);
954
+ border-radius: var(--vgai-radius-md);
955
+ background: var(--vgai-surface-inset);
956
+ }
957
+ .vgai-generation-document-empty,
958
+ .vgai-generation-result-empty {
959
+ padding: var(--vgai-space-5);
960
+ color: var(--vgai-content-muted);
961
+ }
962
+ .vgai-generation-native-result {
963
+ margin: 0;
964
+ overflow: auto;
965
+ color: var(--vgai-content-muted);
966
+ font-size: var(--vgai-font-sm);
967
+ white-space: pre-wrap;
968
+ overflow-wrap: anywhere;
969
+ }
970
+ .vgai-generation-image-grid {
971
+ display: grid;
972
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
973
+ gap: var(--vgai-space-3);
974
+ }
975
+ .vgai-generation-image-grid img,
976
+ .vgai-generation-panorama,
977
+ .vgai-generation-world-result img {
978
+ display: block;
979
+ width: 100%;
980
+ max-height: 620px;
981
+ border-radius: var(--vgai-radius-sm);
982
+ object-fit: contain;
983
+ }
984
+ .vgai-generation-media {
985
+ display: block;
986
+ width: min(100%, 860px);
987
+ margin: auto;
988
+ }
989
+ .vgai-generation-result-links,
990
+ .vgai-generation-world-result,
991
+ .vgai-generation-output-paths {
992
+ display: grid;
993
+ align-content: start;
994
+ justify-items: start;
995
+ gap: var(--vgai-space-3);
996
+ }
997
+ .vgai-generation-output-paths code {
998
+ color: var(--vgai-content-muted);
999
+ overflow-wrap: anywhere;
1000
+ }
1001
+ .vgai-generation-document-footer {
1002
+ display: flex;
1003
+ justify-content: flex-end;
1004
+ }
1005
+ .vgai-generation-create-grid {
1006
+ display: grid;
1007
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
1008
+ gap: var(--vgai-space-2);
1009
+ }
1010
+ .vgai-generation-create-card {
1011
+ display: grid;
1012
+ gap: var(--vgai-space-1);
1013
+ padding: var(--vgai-space-3);
1014
+ border: var(--vgai-stroke-resting) solid var(--vgai-boundary-default);
1015
+ border-radius: var(--vgai-radius-sm);
1016
+ color: var(--vgai-content-primary);
1017
+ text-align: left;
1018
+ background: var(--vgai-surface-raised);
1019
+ cursor: pointer;
1020
+ }
1021
+ .vgai-generation-create-card:hover {
1022
+ border-color: var(--vgai-boundary-strong);
1023
+ background: var(--vgai-neutral-hover);
1024
+ }
1025
+ .vgai-generation-create-card:focus-visible {
1026
+ outline: var(--vgai-focus-ring);
1027
+ outline-offset: 1px;
1028
+ }
1029
+ .vgai-generation-create-card span {
1030
+ color: var(--vgai-content-muted);
1031
+ font-size: var(--vgai-font-sm);
1032
+ }
1033
+
1034
+ /* Live account markup class (account-documents.tsx); its visual treatment
1035
+ * lives in product-shell.css — this only provides the card's internal flow.
1036
+ * The rest of the legacy account vocabulary (.vgai-account-summary-grid,
1037
+ * .vgai-provider-connection singular, …) was dead and has been pruned. */
1038
+ .vgai-provider-connections {
1039
+ display: grid;
1040
+ gap: var(--vgai-space-4);
1041
+ }
1042
+
1043
+ @media (max-width: 720px) {
1044
+ .vgai-tool-page {
1045
+ padding: var(--vgai-space-4);
1046
+ }
1047
+ .vgai-generation-document {
1048
+ padding: var(--vgai-space-4);
1049
+ }
1050
+ .vgai-generation-document-header {
1051
+ display: grid;
1052
+ }
1053
+ }
1054
+
1055
+ .vgai-checkbox {
1056
+ accent-color: var(--vgai-accent);
1057
+ width: 13px;
1058
+ height: 13px;
1059
+ cursor: pointer;
1060
+ margin: 0;
1061
+ }
1062
+ .vgai-checkbox:focus-visible {
1063
+ outline: var(--vgai-focus-ring);
1064
+ outline-offset: 2px;
1065
+ }
1066
+ .vgai-checkbox:indeterminate {
1067
+ accent-color: var(--vgai-warn);
1068
+ }
1069
+ .vgai-checkbox:disabled {
1070
+ opacity: 0.5;
1071
+ cursor: not-allowed;
1072
+ }
1073
+
1074
+ .vgai-color-swatch {
1075
+ width: var(--vgai-control-compact-height);
1076
+ height: var(--vgai-control-compact-height);
1077
+ flex: 0 0 auto;
1078
+ padding: 0;
1079
+ overflow: hidden;
1080
+ background: transparent;
1081
+ border: var(--vgai-stroke-resting) solid var(--vgai-boundary-default);
1082
+ border-radius: var(--vgai-radius-sm);
1083
+ cursor: pointer;
1084
+ }
1085
+ .vgai-color-swatch:hover:not(:disabled) {
1086
+ border-color: var(--vgai-boundary-strong);
1087
+ }
1088
+ .vgai-color-swatch:focus-visible,
1089
+ .vgai-file-input:focus-visible,
1090
+ .vgai-range:focus-visible {
1091
+ outline: var(--vgai-focus-ring);
1092
+ outline-offset: 2px;
1093
+ }
1094
+ .vgai-color-swatch:disabled,
1095
+ .vgai-file-input:disabled,
1096
+ .vgai-range:disabled {
1097
+ opacity: 0.5;
1098
+ cursor: not-allowed;
1099
+ }
1100
+ .vgai-file-input {
1101
+ color: var(--vgai-content-muted);
1102
+ font: inherit;
1103
+ }
1104
+ .vgai-range {
1105
+ min-width: 0;
1106
+ accent-color: var(--vgai-accent);
1107
+ }
1108
+
1109
+ .vgai-number-input,
1110
+ .vgai-color-input {
1111
+ display: flex;
1112
+ min-width: 0;
1113
+ align-items: center;
1114
+ gap: var(--vgai-space-2);
1115
+ }
1116
+ .vgai-number-input-label {
1117
+ width: 12px;
1118
+ flex: 0 0 auto;
1119
+ color: var(--vgai-content-muted);
1120
+ font-size: var(--vgai-font-sm);
1121
+ font-weight: 600;
1122
+ text-align: center;
1123
+ user-select: none;
1124
+ }
1125
+ .vgai-number-input-editor,
1126
+ .vgai-number-input-scrub,
1127
+ .vgai-color-input > .vgai-input {
1128
+ width: 100%;
1129
+ min-width: 0;
1130
+ }
1131
+ .vgai-number-input-scrub {
1132
+ box-sizing: border-box;
1133
+ overflow: hidden;
1134
+ padding: var(--vgai-space-1) var(--vgai-space-3);
1135
+ color: var(--vgai-content-primary);
1136
+ background: var(--vgai-input-surface, var(--vgai-surface-inset));
1137
+ border: var(--vgai-stroke-resting) solid var(--vgai-input-border, var(--vgai-boundary-default));
1138
+ border-radius: var(--vgai-radius-sm);
1139
+ box-shadow: var(--vgai-input-shadow, none);
1140
+ font-size: var(--vgai-font-base);
1141
+ text-overflow: ellipsis;
1142
+ white-space: nowrap;
1143
+ cursor: ew-resize;
1144
+ user-select: none;
1145
+ }
1146
+ .vgai-number-input-scrub:hover {
1147
+ border-color: var(--vgai-boundary-strong);
1148
+ }
1149
+ .vgai-scrub-label {
1150
+ transition:
1151
+ color 0.12s var(--vgai-ease-standard),
1152
+ filter 0.12s var(--vgai-ease-standard);
1153
+ }
1154
+ .vgai-scrub-label {
1155
+ color: var(--vgai-content-muted);
1156
+ }
1157
+ .vgai-scrub-label:hover:not([data-disabled="true"]) {
1158
+ color: var(--vgai-content-primary);
1159
+ }
1160
+ .vgai-number-input-scrub[data-disabled] {
1161
+ opacity: 0.5;
1162
+ cursor: not-allowed;
1163
+ }
1164
+ .vgai-number-input-scrub[data-mixed="true"],
1165
+ .vgai-color-input > .vgai-input[data-mixed="true"] {
1166
+ color: var(--vgai-content-dim);
1167
+ font-style: italic;
1168
+ }
1169
+ .vgai-color-input-mixed {
1170
+ width: var(--vgai-control-compact-height);
1171
+ height: var(--vgai-control-compact-height);
1172
+ flex: 0 0 auto;
1173
+ border: var(--vgai-stroke-resting) solid var(--vgai-boundary-default);
1174
+ border-radius: var(--vgai-radius-sm);
1175
+ background-color: var(--vgai-surface-inset);
1176
+ background-image:
1177
+ linear-gradient(
1178
+ 45deg,
1179
+ var(--vgai-content-dim) 25%,
1180
+ transparent 25%,
1181
+ transparent 75%,
1182
+ var(--vgai-content-dim) 75%
1183
+ ),
1184
+ linear-gradient(
1185
+ 45deg,
1186
+ var(--vgai-content-dim) 25%,
1187
+ transparent 25%,
1188
+ transparent 75%,
1189
+ var(--vgai-content-dim) 75%
1190
+ );
1191
+ background-position:
1192
+ 0 0,
1193
+ 4px 4px;
1194
+ background-size: 8px 8px;
1195
+ }
1196
+
1197
+ .vgai-section {
1198
+ border-bottom: var(--vgai-stroke-resting) solid var(--vgai-structural-divider);
1199
+ }
1200
+ .vgai-section-header {
1201
+ display: flex;
1202
+ width: 100%;
1203
+ min-height: var(--vgai-control-default-height);
1204
+ align-items: center;
1205
+ gap: var(--vgai-space-1);
1206
+ color: var(--vgai-content-primary);
1207
+ /* A header is a slab lifted off its panel (Blender's header sits a step above its panel),
1208
+ derived from the palette's own two surfaces so every skin keeps the
1209
+ relationship. */
1210
+ background: var(--vgai-surface-section);
1211
+ }
1212
+ .vgai-section-trigger {
1213
+ display: flex;
1214
+ min-width: 0;
1215
+ min-height: var(--vgai-control-default-height);
1216
+ flex: 1;
1217
+ align-items: center;
1218
+ gap: var(--vgai-space-2);
1219
+ padding: var(--vgai-space-2) var(--vgai-space-4);
1220
+ color: inherit;
1221
+ background: transparent;
1222
+ border: 0;
1223
+ font: inherit;
1224
+ font-size: var(--vgai-font-base);
1225
+ font-weight: 600;
1226
+ text-align: left;
1227
+ cursor: pointer;
1228
+ }
1229
+ .vgai-section-trigger:hover {
1230
+ background: var(--vgai-neutral-hover);
1231
+ }
1232
+ /* A section heading that cannot collapse is a label, not a control: no
1233
+ disclosure arrow, no pointer, no hover feedback promising a toggle. */
1234
+ .vgai-section-trigger[data-vgai-static],
1235
+ .vgai-section-trigger[data-vgai-static]:hover {
1236
+ cursor: default;
1237
+ background: transparent;
1238
+ }
1239
+ .vgai-section-trigger:focus-visible {
1240
+ outline: var(--vgai-focus-ring);
1241
+ outline-offset: -2px;
1242
+ }
1243
+ .vgai-section-title {
1244
+ display: grid;
1245
+ min-width: 0;
1246
+ flex: 1;
1247
+ }
1248
+ .vgai-section-title > span,
1249
+ .vgai-section-title > small {
1250
+ overflow: hidden;
1251
+ text-overflow: ellipsis;
1252
+ white-space: nowrap;
1253
+ }
1254
+ .vgai-section-title > small {
1255
+ color: var(--vgai-content-dim);
1256
+ font-size: var(--vgai-font-sm);
1257
+ font-weight: 400;
1258
+ }
1259
+ .vgai-section-actions {
1260
+ display: flex;
1261
+ flex: 0 0 auto;
1262
+ align-items: center;
1263
+ gap: var(--vgai-space-1);
1264
+ padding-right: var(--vgai-space-2);
1265
+ }
1266
+ .vgai-section-body {
1267
+ padding: 0 var(--vgai-space-4) var(--vgai-space-4);
1268
+ }
1269
+ /* One contributed inspector section's frame (`components/InspectorToolSection.tsx`).
1270
+ These three values were inline literals on that element; they are the same
1271
+ three, tokenized, so the narrow column is unchanged — the point of moving
1272
+ them is that the Properties presentation can now REPLACE them (Blender's
1273
+ Properties editor has no rule between its datablock row and its first panel,
1274
+ and insets its panel column once, not twice; `compact-inspector.css`). */
1275
+ .vgai-inspector-tool-section {
1276
+ padding: var(--vgai-space-4);
1277
+ margin-top: var(--vgai-space-2);
1278
+ border-top: var(--vgai-stroke-resting) solid var(--vgai-structural-divider);
1279
+ }
1280
+
1281
+ .vgai-xstate-node {
1282
+ display: flex;
1283
+ flex-direction: column;
1284
+ align-items: flex-start;
1285
+ justify-content: center;
1286
+ gap: var(--vgai-space-1);
1287
+ padding: var(--vgai-space-3) var(--vgai-space-5);
1288
+ color: var(--vgai-content-primary);
1289
+ background: var(--vgai-surface-panel);
1290
+ border: var(--vgai-stroke-resting) solid var(--vgai-boundary-strong);
1291
+ font-size: var(--vgai-font-base);
1292
+ text-align: left;
1293
+ transition:
1294
+ background 0.1s var(--vgai-ease-standard),
1295
+ border-color 0.1s var(--vgai-ease-standard),
1296
+ box-shadow 0.1s var(--vgai-ease-standard);
1297
+ }
1298
+ .vgai-xstate-node:hover {
1299
+ background: var(--vgai-surface-chrome);
1300
+ }
1301
+ .vgai-xstate-node[data-active="true"] {
1302
+ border-color: var(--vgai-success);
1303
+ }
1304
+ .vgai-xstate-node[data-selected="true"] {
1305
+ border-color: var(--vgai-selection-border);
1306
+ box-shadow: 0 0 0 var(--vgai-stroke-resting) var(--vgai-selection-border);
1307
+ }
1308
+ .vgai-xstate-node-type {
1309
+ color: var(--vgai-content-dim);
1310
+ transition: color 0.1s var(--vgai-ease-standard);
1311
+ }
1312
+ .vgai-xstate-node:hover .vgai-xstate-node-type,
1313
+ .vgai-xstate-node[data-selected="true"] .vgai-xstate-node-type {
1314
+ color: var(--vgai-content-primary);
1315
+ }
1316
+
1317
+ /* ---------------------------------------------------------------------- */
1318
+ /* Layout, typography, and surface primitives. Product code composes these */
1319
+ /* roles instead of restating their visual recipes in inline objects. */
1320
+ /* ---------------------------------------------------------------------- */
1321
+
1322
+ .vgai-stack {
1323
+ display: flex;
1324
+ min-width: 0;
1325
+ flex-direction: column;
1326
+ }
1327
+ .vgai-inline {
1328
+ display: flex;
1329
+ min-width: 0;
1330
+ }
1331
+ .vgai-spacer {
1332
+ min-width: 0;
1333
+ flex: 1 1 auto;
1334
+ }
1335
+ .vgai-actions {
1336
+ flex: 0 0 auto;
1337
+ }
1338
+ .vgai-divider {
1339
+ flex: 0 0 auto;
1340
+ background: var(--vgai-boundary-default);
1341
+ }
1342
+ .vgai-divider[data-orientation="horizontal"] {
1343
+ width: 100%;
1344
+ height: var(--vgai-stroke-resting);
1345
+ }
1346
+ .vgai-divider[data-orientation="vertical"] {
1347
+ width: var(--vgai-stroke-resting);
1348
+ /* A rule between controls is exactly as tall as the controls it separates —
1349
+ measured in Blender's top bar, the rule closing the menu words is 20px,
1350
+ the same as every widget in that bar. It was a hard 16px, a literal that
1351
+ outranked whatever height the material asked for. */
1352
+ height: var(--vgai-control-compact-height);
1353
+ }
1354
+
1355
+ .vgai-text {
1356
+ min-width: 0;
1357
+ margin: 0;
1358
+ color: var(--vgai-content-primary);
1359
+ font-size: var(--vgai-font-base);
1360
+ font-weight: 400;
1361
+ line-height: 1.35;
1362
+ }
1363
+ .vgai-text[data-variant="caption"] {
1364
+ font-size: var(--vgai-font-sm);
1365
+ }
1366
+ .vgai-text[data-variant="body"] {
1367
+ font-size: var(--vgai-font-base);
1368
+ }
1369
+ .vgai-text[data-variant="label"] {
1370
+ font-size: var(--vgai-font-base);
1371
+ font-weight: 600;
1372
+ }
1373
+ .vgai-text[data-variant="title"] {
1374
+ font-size: var(--vgai-font-md);
1375
+ font-weight: 600;
1376
+ }
1377
+ .vgai-text[data-variant="heading"] {
1378
+ font-size: var(--vgai-font-xl);
1379
+ font-weight: 600;
1380
+ }
1381
+ .vgai-text[data-variant="display"] {
1382
+ font-size: var(--vgai-font-heading);
1383
+ font-weight: 600;
1384
+ }
1385
+ .vgai-text[data-variant="code"] {
1386
+ font-family: var(--vgai-font-mono);
1387
+ font-size: var(--vgai-font-base);
1388
+ }
1389
+ .vgai-text[data-tone="muted"] {
1390
+ color: var(--vgai-content-muted);
1391
+ }
1392
+ .vgai-text[data-tone="dim"] {
1393
+ color: var(--vgai-content-dim);
1394
+ }
1395
+ .vgai-text[data-tone="accent"] {
1396
+ color: var(--vgai-accent);
1397
+ }
1398
+ .vgai-text[data-tone="danger"] {
1399
+ color: var(--vgai-danger);
1400
+ }
1401
+ .vgai-text[data-tone="warning"] {
1402
+ color: var(--vgai-warn);
1403
+ }
1404
+ .vgai-text[data-tone="success"] {
1405
+ color: var(--vgai-success);
1406
+ }
1407
+ .vgai-text[data-truncate="true"] {
1408
+ overflow: hidden;
1409
+ text-overflow: ellipsis;
1410
+ white-space: nowrap;
1411
+ }
1412
+ .vgai-text[data-selectable="true"] {
1413
+ user-select: text;
1414
+ }
1415
+
1416
+ .vgai-surface {
1417
+ min-width: 0;
1418
+ color: var(--vgai-content-primary);
1419
+ background: var(--vgai-surface-panel);
1420
+ box-sizing: border-box;
1421
+ }
1422
+ .vgai-surface[data-variant="shell"] {
1423
+ background: var(--vgai-surface-shell);
1424
+ }
1425
+ .vgai-surface[data-variant="panel"] {
1426
+ background: var(--vgai-surface-panel);
1427
+ }
1428
+ .vgai-surface[data-variant="chrome"] {
1429
+ background: var(--vgai-surface-chrome);
1430
+ }
1431
+ .vgai-surface[data-variant="raised"] {
1432
+ background: var(--vgai-surface-raised);
1433
+ }
1434
+ .vgai-surface[data-variant="inset"] {
1435
+ background: var(--vgai-surface-inset);
1436
+ }
1437
+ .vgai-surface[data-variant="overlay"] {
1438
+ background: var(--vgai-surface-overlay);
1439
+ }
1440
+ .vgai-surface[data-border="true"] {
1441
+ border: var(--vgai-stroke-resting) solid var(--vgai-boundary-default);
1442
+ }
1443
+ .vgai-surface[data-scroll="true"] {
1444
+ min-height: 0;
1445
+ overflow: auto;
1446
+ }
1447
+
1448
+ /* Modal composition. Product dialogs supply content; this family owns chrome.
1449
+ Glass themes pair a LIGHTER dim with a scrim blur (the macOS modal read,
1450
+ P6-U6 owner taste decision 3) — the blur var is `none` for every
1451
+ treatment-less theme, whose heavier `--vgai-scrim` dim then paints
1452
+ byte-identically to before. */
1453
+ .vgai-dialog-scrim {
1454
+ position: fixed;
1455
+ inset: 0;
1456
+ z-index: var(--vgai-z-modal);
1457
+ display: flex;
1458
+ align-items: center;
1459
+ justify-content: center;
1460
+ padding: var(--vgai-space-10);
1461
+ background: var(--vgai-scrim);
1462
+ backdrop-filter: var(--vgai-scrim-backdrop-filter, none);
1463
+ -webkit-backdrop-filter: var(--vgai-scrim-backdrop-filter, none);
1464
+ pointer-events: auto;
1465
+ box-sizing: border-box;
1466
+ }
1467
+ .vgai-dialog-scrim[data-variant="command"] {
1468
+ align-items: flex-start;
1469
+ padding-top: calc(var(--vgai-space-12) + var(--vgai-space-10) + var(--vgai-space-8));
1470
+ }
1471
+ .vgai-dialog {
1472
+ display: flex;
1473
+ width: min(520px, calc(100vw - 48px));
1474
+ max-height: calc(100vh - 48px);
1475
+ min-width: 360px;
1476
+ flex-direction: column;
1477
+ overflow: hidden;
1478
+ color: var(--vgai-content-primary);
1479
+ background: var(--vgai-glass-regular-bg, var(--vgai-surface-chrome));
1480
+ border: var(--vgai-stroke-resting) solid var(--vgai-boundary-strong);
1481
+ border-radius: var(--vgai-radius-lg);
1482
+ box-shadow: var(--vgai-shadow-lg);
1483
+ box-sizing: border-box;
1484
+ }
1485
+ .vgai-dialog[data-size="compact"] {
1486
+ width: min(420px, calc(100vw - 48px));
1487
+ }
1488
+ .vgai-dialog[data-size="wide"] {
1489
+ width: min(920px, calc(100vw - 48px));
1490
+ height: min(720px, calc(100vh - 48px));
1491
+ }
1492
+ .vgai-dialog[data-variant="command"] {
1493
+ width: min(500px, calc(100vw - 48px));
1494
+ max-height: min(480px, calc(100vh - 48px));
1495
+ }
1496
+ .vgai-command-results {
1497
+ min-height: 0;
1498
+ max-height: 400px;
1499
+ flex: 1;
1500
+ overflow-y: auto;
1501
+ }
1502
+ .vgai-command-category {
1503
+ padding: var(--vgai-space-3) calc(var(--vgai-space-6) + var(--vgai-space-1)) var(--vgai-space-1);
1504
+ color: var(--vgai-content-muted);
1505
+ font-size: var(--vgai-font-base);
1506
+ font-weight: 600;
1507
+ letter-spacing: 0.5px;
1508
+ text-transform: uppercase;
1509
+ }
1510
+ .vgai-command-row {
1511
+ display: flex;
1512
+ align-items: center;
1513
+ gap: var(--vgai-space-4);
1514
+ padding: 5px calc(var(--vgai-space-6) + var(--vgai-space-1));
1515
+ cursor: pointer;
1516
+ }
1517
+ .vgai-command-row[data-highlighted="true"] {
1518
+ background: var(--vgai-selection-bg);
1519
+ }
1520
+ .vgai-command-icon {
1521
+ width: 14px;
1522
+ flex-shrink: 0;
1523
+ color: var(--vgai-content-dim);
1524
+ font-size: var(--vgai-font-sm);
1525
+ text-align: center;
1526
+ }
1527
+ .vgai-command-label {
1528
+ min-width: 0;
1529
+ flex: 1;
1530
+ overflow: hidden;
1531
+ text-overflow: ellipsis;
1532
+ white-space: nowrap;
1533
+ }
1534
+ .vgai-command-shortcut {
1535
+ flex-shrink: 0;
1536
+ color: var(--vgai-content-dim);
1537
+ font-size: var(--vgai-font-base);
1538
+ }
1539
+ .vgai-command-empty {
1540
+ padding: var(--vgai-space-8) calc(var(--vgai-space-6) + var(--vgai-space-1));
1541
+ color: var(--vgai-content-dim);
1542
+ text-align: center;
1543
+ }
1544
+ .vgai-dialog-header {
1545
+ padding: var(--vgai-space-8) var(--vgai-space-10);
1546
+ border-bottom: var(--vgai-stroke-resting) solid var(--vgai-structural-divider);
1547
+ }
1548
+ .vgai-dialog-body {
1549
+ min-height: 0;
1550
+ padding: var(--vgai-space-8) var(--vgai-space-10);
1551
+ overflow: auto;
1552
+ }
1553
+ .vgai-dialog-footer {
1554
+ justify-content: flex-end;
1555
+ padding: var(--vgai-space-6) var(--vgai-space-10);
1556
+ border-top: var(--vgai-stroke-resting) solid var(--vgai-boundary-default);
1557
+ }
1558
+ .vgai-dialog-field {
1559
+ display: grid;
1560
+ min-width: 0;
1561
+ gap: var(--vgai-space-2);
1562
+ }
1563
+ .vgai-dialog-field > .vgai-input {
1564
+ width: 100%;
1565
+ }
1566
+ .vgai-joined-field {
1567
+ align-items: stretch;
1568
+ gap: 0;
1569
+ }
1570
+ .vgai-joined-field > .vgai-input {
1571
+ width: 100%;
1572
+ border-top-right-radius: 0;
1573
+ border-bottom-right-radius: 0;
1574
+ }
1575
+ .vgai-joined-field-suffix {
1576
+ display: inline-flex;
1577
+ flex: 0 0 auto;
1578
+ align-items: center;
1579
+ padding: 0 var(--vgai-space-4);
1580
+ color: var(--vgai-content-dim);
1581
+ background: var(--vgai-surface-raised);
1582
+ border: var(--vgai-stroke-resting) solid var(--vgai-boundary-default);
1583
+ border-left: 0;
1584
+ border-radius: 0 var(--vgai-radius-sm) var(--vgai-radius-sm) 0;
1585
+ white-space: nowrap;
1586
+ }
1587
+
1588
+ /* ---------------------------------------------------------------------- */
1589
+ /* .vgai-menu / .vgai-menu-item — shared floating-menu classes (optional, */
1590
+ /* for later units). */
1591
+ /* ---------------------------------------------------------------------- */
1592
+
1593
+ /* REGULAR Liquid Glass (P3 overlay chrome, formalized as
1594
+ * `--vgai-glass-regular-*` in P6-U6 — the owner's Apple-HIG call: menus/
1595
+ * popovers/dialogs/palette are the REGULAR variant, "more solid", never the
1596
+ * refractive Clear family): transient surfaces are never
1597
+ * backdrop-classified — the material is self-sufficient on both luminance
1598
+ * sides instead. `--vgai-glass-regular-bg` is a raised light tint (adaptive
1599
+ * themes) or the theme's own chrome tone at high opacity (dark-side glass
1600
+ * themes) over a strong frost (`…-backdrop-filter`), statically paired with
1601
+ * the `-on-bright` ink ramp so they read over ANY backdrop (the ramp
1602
+ * mirrors the normal inks for non-adaptive themes, making the remap
1603
+ * inert). For every theme without a treatment these vars mirror the plain
1604
+ * chrome surface / `none` — byte-identical paint. Same text-shadow
1605
+ * re-declaration rationale as the bright-scope rule above. */
1606
+ .vgai-menu,
1607
+ .vgai-popover,
1608
+ .vgai-dialog {
1609
+ --vgai-content-primary: var(--vgai-content-primary-on-bright);
1610
+ --vgai-content-muted: var(--vgai-content-muted-on-bright);
1611
+ --vgai-content-dim: var(--vgai-content-dim-on-bright);
1612
+ --vgai-content-text-shadow: var(--vgai-content-text-shadow-on-bright);
1613
+ --vgai-content-frost-bg: var(--vgai-content-frost-bg-on-bright);
1614
+ --vgai-text-1: var(--vgai-content-primary);
1615
+ --vgai-text-2: var(--vgai-content-muted);
1616
+ --vgai-text-3: var(--vgai-content-dim);
1617
+ text-shadow: var(--vgai-content-text-shadow, none);
1618
+ backdrop-filter: var(--vgai-glass-regular-backdrop-filter, none);
1619
+ -webkit-backdrop-filter: var(--vgai-glass-regular-backdrop-filter, none);
1620
+ }
1621
+
1622
+ .vgai-menu {
1623
+ /* Opaque skins: the shell, a step darker than the bar a menu opens from
1624
+ (Blender's popups are a step darker than its top bar). Glass keeps its own. */
1625
+ background: var(--vgai-glass-regular-bg, var(--vgai-surface-shell));
1626
+ border: 1px solid var(--vgai-border-1);
1627
+ border-radius: var(--vgai-radius-md);
1628
+ box-shadow: var(--vgai-shadow-md);
1629
+ padding: var(--vgai-space-2);
1630
+ z-index: var(--vgai-z-dropdown);
1631
+ /* Menus can portal directly under the theme root to escape a glass
1632
+ stacking context. Own typography at the primitive boundary instead of
1633
+ inheriting through `.vgai-shell`, or portaled dropdowns fall back to the
1634
+ browser's serif default. */
1635
+ }
1636
+ .vgai-menu-item {
1637
+ display: flex;
1638
+ align-items: center;
1639
+ width: 100%;
1640
+ min-height: var(--vgai-control-default-height);
1641
+ box-sizing: border-box;
1642
+ border: 0;
1643
+ background: transparent;
1644
+ padding: var(--vgai-space-2) var(--vgai-space-4);
1645
+ border-radius: var(--vgai-radius-sm);
1646
+ font-size: var(--vgai-font-base);
1647
+ color: var(--vgai-text-1);
1648
+ cursor: pointer;
1649
+ user-select: none;
1650
+ font-family: inherit;
1651
+ text-align: left;
1652
+ white-space: nowrap;
1653
+ }
1654
+ .vgai-menu-item:hover {
1655
+ background: var(--vgai-selection-bg);
1656
+ }
1657
+ .vgai-menu-item:focus-visible {
1658
+ outline: var(--vgai-focus-ring);
1659
+ outline-offset: -1px;
1660
+ }
1661
+ .vgai-menu-item:disabled {
1662
+ color: var(--vgai-text-3);
1663
+ cursor: default;
1664
+ }
1665
+ .vgai-menu-item:disabled:hover {
1666
+ background: transparent;
1667
+ }
1668
+ .vgai-menu-separator {
1669
+ height: var(--vgai-stroke-resting);
1670
+ margin: var(--vgai-space-1) var(--vgai-space-2);
1671
+ border: 0;
1672
+ background: var(--vgai-boundary-default);
1673
+ }
1674
+
1675
+ /* A top-bar menu word (File/Edit/Render in Blender; Edit/View/Window here) is
1676
+ a WIDGET, not a line of text. Measured in Blender 5.2's own top bar at 2x
1677
+ (`topbar.png`, halved): the labels sit 16px ink-to-ink — 8px of padding
1678
+ each side with the buttons touching — and every widget in the bar is 20px
1679
+ tall. This rule used to give 6px of padding and no height at all, so the
1680
+ trigger collapsed to its 15px line box (font 11 x leading 1.4) and the row
1681
+ read as text floating in a bar. `min-height` rather than `height`: where a
1682
+ parent DOES have a definite height (a panel header), 100% still wins. */
1683
+ .vgai-menu-trigger {
1684
+ height: 100%;
1685
+ min-height: var(--vgai-control-compact-height);
1686
+ border: 0;
1687
+ border-radius: var(--vgai-radius-sm);
1688
+ background: transparent;
1689
+ /* A MENU WORD IS ITS OWN INK (`content.menu`; see theme.ts for the two
1690
+ backdrops that prove it opaque). This rule used to read `text-2` — the
1691
+ OUTLINER ROW's ink — so every unenclosed clickable word in an area header
1692
+ drew at row-text level while the top bar alone carried an override to
1693
+ primary: one role split across two members. The token falls back to
1694
+ `content.primary` in the emitter, which is what the top bar already
1695
+ painted, so a palette naming no menu ink leaves that bar untouched. */
1696
+ color: var(--vgai-content-menu);
1697
+ padding: 0 var(--vgai-space-4);
1698
+ cursor: pointer;
1699
+ font: inherit;
1700
+ }
1701
+ .vgai-menu-trigger:hover,
1702
+ .vgai-menu-trigger[aria-expanded="true"] {
1703
+ color: var(--vgai-text-1);
1704
+ background: var(--vgai-bg-3);
1705
+ }
1706
+ .vgai-menu-trigger:focus-visible {
1707
+ outline: var(--vgai-focus-ring);
1708
+ outline-offset: -1px;
1709
+ }
1710
+
1711
+ /* Persistent REGULAR Liquid Glass: large, text-heavy panels blur and adapt the
1712
+ * sampled backdrop instead of replacing it with a high-opacity card. The
1713
+ * normal light-ink state adds shade only when contrast begins to fail; bright
1714
+ * scenes switch to dark ink and nearly transparent fills. The panel host
1715
+ * owns the ONE body frost; dense `.vgai-content-frost` floors receive a
1716
+ * stronger correction, but never a second filter. */
1717
+ [data-vgai-chrome="islands"] .vgai-glass-regular-panel {
1718
+ color: var(--vgai-content-primary);
1719
+ text-shadow: var(--vgai-content-text-shadow, none);
1720
+ background: var(
1721
+ --vgai-glass-regular-panel-adaptive-bg,
1722
+ var(--vgai-glass-regular-panel-bg, var(--vgai-surface-panel))
1723
+ );
1724
+ backdrop-filter: var(--vgai-glass-regular-panel-backdrop-filter, none);
1725
+ -webkit-backdrop-filter: var(--vgai-glass-regular-panel-backdrop-filter, none);
1726
+ border: var(--vgai-stroke-resting) solid
1727
+ color-mix(in srgb, var(--vgai-content-primary) 22%, transparent);
1728
+ border-radius: var(--vgai-radius-lg);
1729
+ box-shadow: var(--vgai-card-specular-shadow, var(--vgai-shadow-lg));
1730
+ }
1731
+
1732
+ [data-vgai-chrome="islands"] .vgai-glass-regular-panel .vgai-content-frost {
1733
+ background: var(
1734
+ --vgai-glass-regular-content-adaptive-bg,
1735
+ var(--vgai-content-frost-bg, transparent)
1736
+ );
1737
+ backdrop-filter: none;
1738
+ -webkit-backdrop-filter: none;
1739
+ }
1740
+
1741
+ /* ---------------------------------------------------------------------- */
1742
+ /* Editor composition patterns: toolbars, tabs, trees, fields, and states. */
1743
+ /* ---------------------------------------------------------------------- */
1744
+
1745
+ .vgai-toolbar {
1746
+ display: flex;
1747
+ min-width: 0;
1748
+ min-height: var(--vgai-local-toolbar-height);
1749
+ align-items: center;
1750
+ gap: var(--vgai-space-2);
1751
+ padding: 0 var(--vgai-space-3);
1752
+ color: var(--vgai-content-primary);
1753
+ /* Structural chrome is clear. Significant text floors own the single
1754
+ content-frost tier; a toolbar inside that floor must not blur again.
1755
+ Floating toolbars below remain independent glass islands. */
1756
+ background: transparent;
1757
+ border-bottom: var(--vgai-stroke-resting) solid var(--vgai-structural-divider);
1758
+ box-sizing: border-box;
1759
+ }
1760
+ .vgai-toolbar[data-compact="true"] {
1761
+ min-height: var(--vgai-control-default-height);
1762
+ padding-inline: var(--vgai-space-2);
1763
+ }
1764
+ .vgai-toolbar-group {
1765
+ display: inline-flex;
1766
+ min-width: 0;
1767
+ align-items: center;
1768
+ gap: var(--vgai-space-2);
1769
+ }
1770
+ .vgai-toolbar-divider {
1771
+ width: 1px;
1772
+ height: 16px;
1773
+ flex: 0 0 auto;
1774
+ margin: 0 var(--vgai-space-1);
1775
+ background: var(--vgai-boundary-default);
1776
+ }
1777
+ /* Floating toolbars are glass ISLANDS (P6 glass-native chrome, U2): the
1778
+ owner's "just transparent blurs" call-out. Fill, filter, radius, border,
1779
+ and shadow all arrive through the treatment-driven --vgai-island-* vars
1780
+ (theme.ts) — a glass theme paints the card material (panel-whisper fill,
1781
+ the theme's plain backdrop list, radius 17, deep shadow) and the
1782
+ refraction engine upgrades each `.vgai-glass-island` (the class
1783
+ patterns/Toolbar.tsx adds alongside this one) to its own url()
1784
+ displacement chain where capable; this declaration is the var-driven
1785
+ plain-list fallback it suspends to. Treatment-less themes emit the exact
1786
+ pre-P6 recipe through the same vars, so graphite/lite paint
1787
+ byte-identically. Text opts into the theme's content shadow (inert
1788
+ 'none' without the axis). */
1789
+ .vgai-floating-toolbar {
1790
+ position: relative;
1791
+ min-height: auto;
1792
+ padding: var(--vgai-space-2) var(--vgai-space-3);
1793
+ border: var(--vgai-stroke-resting) solid
1794
+ var(--vgai-island-border-color, var(--vgai-boundary-strong));
1795
+ border-radius: var(--vgai-island-radius, var(--vgai-radius-md));
1796
+ /* Ambient-lift layer (P6-U6 taste decision 2): 'none' for every
1797
+ treatment-less theme, so the composed background stays byte-equivalent
1798
+ to the single-layer paint. */
1799
+ background:
1800
+ var(--vgai-ambient-lift, none), var(--vgai-backdrop-dim, none),
1801
+ var(--vgai-island-surface, color-mix(in srgb, var(--vgai-surface-panel) 90%, transparent));
1802
+ box-shadow: var(--vgai-island-shadow, var(--vgai-shadow-sm));
1803
+ backdrop-filter: var(--vgai-island-backdrop-filter, blur(8px));
1804
+ -webkit-backdrop-filter: var(--vgai-island-backdrop-filter, blur(8px));
1805
+ text-shadow: var(--vgai-content-text-shadow, none);
1806
+ pointer-events: auto;
1807
+ }
1808
+ /* Apple-style segmented chrome uses a neutral material lift for selection.
1809
+ Accent remains reserved for actions, focus, warnings, and authored color;
1810
+ choosing Move/Rotate/Scale or a viewport display toggle is not an accent
1811
+ action. This toolbar-scoped rule intentionally leaves pressed buttons in
1812
+ ordinary panels on their existing semantic treatment. */
1813
+ .vgai-floating-toolbar .vgai-btn[aria-pressed="true"] {
1814
+ color: var(--vgai-content-primary);
1815
+ background: var(--vgai-selection-bg);
1816
+ border-color: transparent;
1817
+ }
1818
+ /* Exclusive selection uses the same toolbar material and button geometry,
1819
+ but the one selected destination receives the stronger tab-style accent.
1820
+ Independent viewport toggles above intentionally keep the neutral state so
1821
+ several simultaneous selections don't compete for primary emphasis. */
1822
+ .vgai-floating-toolbar[data-selection-mode="single"] .vgai-btn[aria-pressed="true"] {
1823
+ color: var(--vgai-accent);
1824
+ background: var(--vgai-neutral-active);
1825
+ }
1826
+ /* Transform modes are segments, not isolated circular icon buttons. Keep
1827
+ every segment wider than it is tall so the neutral selection reads as a
1828
+ generous oval and changing modes never shifts the toolbar geometry. */
1829
+ .vgai-floating-toolbar .vgai-btn[data-shape="segment"] {
1830
+ width: calc(var(--vgai-control-comfortable-height) + 12px);
1831
+ }
1832
+ /* Specular rim, consistent with cards (workspace-dock.css's card ::before):
1833
+ the 1.5px gradient ring via the padding + mask-composite trick — only the
1834
+ rim band survives the XOR of content-box vs full-box masks. Paints
1835
+ nothing when the theme emits no specular ring ('none' for every
1836
+ treatment-less theme — that IS the degrade). The toolbar's
1837
+ backdrop-filter always makes it a stacking context. The ring sits at z0
1838
+ with interactive children explicitly above it; a huge decorative z-index
1839
+ made Chromium target the toolbar surface instead of its buttons despite
1840
+ `pointer-events:none`. */
1841
+ .vgai-floating-toolbar::before {
1842
+ content: "";
1843
+ position: absolute;
1844
+ inset: 0;
1845
+ z-index: var(--vgai-z-base);
1846
+ padding: 1px;
1847
+ border-radius: inherit;
1848
+ background: var(--vgai-island-specular-ring, none);
1849
+ -webkit-mask:
1850
+ linear-gradient(#000 0 0) content-box,
1851
+ linear-gradient(#000 0 0);
1852
+ -webkit-mask-composite: xor;
1853
+ mask:
1854
+ linear-gradient(#000 0 0) content-box exclude,
1855
+ linear-gradient(#000 0 0);
1856
+ pointer-events: none;
1857
+ }
1858
+ .vgai-floating-toolbar > * {
1859
+ position: relative;
1860
+ z-index: 1;
1861
+ }
1862
+ /* Header/footer chrome ISLANDS (P6 glass-native chrome, U3/U4): under
1863
+ islands chrome — `data-vgai-chrome="islands"` on the theme root, derived
1864
+ in theme.ts from the glass TREATMENT, never from theme identity — the
1865
+ command-bar clusters (ProjectHeader.css) and status-bar groups / tray
1866
+ button (EditorBottomBar.css) paint the same island material as
1867
+ .vgai-floating-toolbar above, through the same --vgai-island-* vars; the
1868
+ refraction engine upgrades each one (they all carry .vgai-glass-island)
1869
+ to the url() displacement chain where capable, and this declaration is
1870
+ the plain-list fallback it suspends to. `:not(:empty)` doubles as the
1871
+ auto-hide affordance: an empty status group paints NOTHING (no 2px
1872
+ border sliver), matching the pre-existing right-group :not(:empty)
1873
+ spacing rule. Under bars chrome the class is completely inert, so
1874
+ graphite/classic and the reduced-transparency/lite fallbacks (which
1875
+ strip `treatment`, and therefore resolve to bars) paint byte-identically
1876
+ to today. Geometry (heights/insets/padding) stays with each component's
1877
+ own stylesheet; only the shared material lives here. */
1878
+ /* THE NAVIGATION CLUSTER's own metrics — Blender's, measured in
1879
+ `modeling-object-none.png` at matched scale: a 28 CSS px button column
1880
+ with 16 px glyphs, in a capsule exactly as wide as the buttons. The
1881
+ Blender style flattens every control height to 20 for its menus and
1882
+ headers, which is right there and wrong here, so the cluster restates
1883
+ the one number it owns rather than bending the style's scale. */
1884
+ .vgai-viewport-navigation {
1885
+ --vgai-control-compact-height: 28px;
1886
+ --vgai-control-default-height: 28px;
1887
+ --vgai-control-comfortable-height: 28px;
1888
+ }
1889
+
1890
+ /* THE GLYPH INK IS 203, MEASURED (2026-09-19, `modeling-edit-none.png`,
1891
+ device x 2774..2805 across all four buttons): mode 203, max 204. Ours
1892
+ inked 194 because the cluster's buttons are `ghost`, and
1893
+ `.vgai-btn[data-variant="ghost"]` paints `content.muted` — which is the
1894
+ TREE ROW TEXT colour. The palette's own record already says that is the
1895
+ wrong family here: `category.tool`'s docblock (`theme.ts`, the derivation
1896
+ from the Properties rail's inactive `#a8a8a8`) states that Blender's
1897
+ hue-less ICON ink is LIGHTER than its row text, and lands on `#cbcbcb`
1898
+ — 203, this exact value, reached from a different frame by a different
1899
+ method. That agreement is what makes 203 a measured Blender ink rather
1900
+ than a number tuned here, and it is also a CORRECTION to the palette
1901
+ settlement's note that 203 is "nine levels from an existing member": the
1902
+ value is not nine levels from one, it IS one.
1903
+ NOT read from `category.tool` and NOT minted. Reading a category tint
1904
+ here would couple a datablock's colour to chrome glyphs — re-tinting the
1905
+ tool category would move these five. Minting costs three homes for one
1906
+ site, which is the refusal the ruler tick's 127 already took. So it is an
1907
+ ALPHA AT THE SITE, which is what the settlement ruled: `content.primary`
1908
+ (229) over this cluster's own plate.
1909
+ THE ARITHMETIC, and its one dependency: the plate composites to 48.6
1910
+ (`island-surface` = `surface-panel` #2f2f2f at 0.9 over the viewport's
1911
+ #3f3f3f), and 0.8556·229 + 0.1444·48.6 = 202.95 -> 203. It is robust to
1912
+ what the viewport paints underneath — over a grid line at 90 the plate
1913
+ reads 51.3 and the glyph still lands 203.3 -> 203 — but NOT to the plate
1914
+ itself moving.
1915
+ THE PLATE ITSELF, RE-MEASURED 2026-09-19, AND THE REFUSAL THAT STOOD HERE
1916
+ WAS WRONG ON BOTH COUNTS. It said Blender's plate is an opaque `#363636`
1917
+ and that "no alpha closes it, 54 has no derivation from a declared
1918
+ member". BLENDER'S PLATE IS TRANSLUCENT: a viewport grid line runs
1919
+ straight through it on `modeling-edit-none.png`, entering the capsule at
1920
+ its left border and leaving at its right, dimmed but unbroken — 54 is a
1921
+ COMPOSITE, not a colour. Two backdrops separate the two, which is the
1922
+ method the Outliner plate and the widget emboss were settled by:
1923
+ * `sculpting.png` has NO grid — 6,032 px of a pure 63 outside the
1924
+ capsule and 3,302 px of a pure 54 inside it.
1925
+ * `modeling-edit-none.png`: a line whose core reads 84-85 outside
1926
+ reads a flat 71 inside, on three independent columns and two
1927
+ crossings. Integrated line MASS (coverage-free) gives 0.794, the
1928
+ peak excess 17/21 = 0.810 — so (1-alpha) is 0.75..0.81.
1929
+ * `modeling-front-ortho.png` is the independent check: one faint line,
1930
+ core 79 outside, 66 inside — ratio 0.75.
1931
+ So the plate is a NEAR-BLACK at alpha ~0.21 (+/-0.03), and 54 = a·P +
1932
+ (1-a)·63 puts P in 16..27. THREE declared members sit in that band, and
1933
+ the measurement cannot separate them — the ROLE does: a plate is a
1934
+ SURFACE, so it is `surface.shell` `#171717` (23), the value Blender's own
1935
+ top bar and status band already take, at alpha 9/40 = 0.225. That
1936
+ reproduces 54 over the viewport EXACTLY and predicts both line cores
1937
+ within one 8-bit level (84 -> 70.3 against 71; 79 -> 66.4 against 66).
1938
+ WHAT THE PRIOR SLOPE OF 0.090 WAS: pairing an outside pixel with an
1939
+ inside pixel at the SAME ROW, across a border crossed by DIAGONAL lines.
1940
+ The two samples are then uncorrelated and a regression's slope collapses
1941
+ toward zero, which is the shape of 0.090 against the 0.79 two
1942
+ independent coverage-free estimators give here.
1943
+ IT IS STILL NOT TAKEN, and the reason is the SITE rather than the value.
1944
+ This rule cannot be gated to one palette (see below), and what it would
1945
+ have to say ungated — "a navigation island is the SHELL at 22.5%" — is a
1946
+ Blender measurement that is false elsewhere: driven to Classic and
1947
+ measured, its cluster plate reads 39 against the same 63 viewport, and
1948
+ shell-at-0.225 would put it at 54.7, dropping its separation from the
1949
+ stage from 24 levels to 8. The knob that could carry it is a per-look
1950
+ ISLAND SURFACE — `--vgai-island-surface` is emitted by `theme.ts`'s
1951
+ `islandSurfaceValue`, which branches on the MATERIAL and hardcodes
1952
+ `surface-panel at 90%` for every non-glass look — and giving a material
1953
+ or palette its own island recipe is a new noun, which is a decision, not
1954
+ a rule here. When it is taken, this alpha becomes 0.8514
1955
+ (229a + 54(1-a) = 203).
1956
+ TWO SELECTOR STEPS PAST THE GHOST RULE, and that is the bought lesson:
1957
+ the ghost rule is (0,2,0) and lives in this same file, so a single-class
1958
+ rule here would be a coin toss on source order.
1959
+ UNGATED, AND I TRIED TO GATE IT FIRST. A `[data-vgai-palette="blender"]`
1960
+ scope was written, and `validate-style-tokens`' `css-style-identity-
1961
+ selector` refused it by name — CSS may not branch on which palette is
1962
+ installed, because that makes every new palette a source edit. The gate
1963
+ is right and the refusal is load-bearing here: it means a
1964
+ palette-measured ALPHA cannot be kept to its own palette at all, which
1965
+ is a CORRECTION to the settlement's note that this value is "closable as
1966
+ an alpha at the site". At the site, yes; at the site FOR ONE PALETTE, no.
1967
+ So this rule joins the block above it on the same standing footing: the
1968
+ cluster's 28 and 16 are Blender's numbers for every skin already, stated
1969
+ there with no palette selector. What it says for a non-Blender skin is
1970
+ true of every skin — this glyph is an ICON, so it takes the primary ink
1971
+ softened onto its own plate, not `content.muted`, which is the TREE ROW
1972
+ TEXT role. `content.primary` is the readable ink in every palette and the
1973
+ mix only moves it toward the plate it sits on, so no skin can lose
1974
+ contrast by it. Classic cold-booted and read after. */
1975
+ .vgai-viewport-navigation .vgai-btn[data-variant="ghost"] {
1976
+ color: color-mix(in srgb, var(--vgai-content-primary) 85.56%, transparent);
1977
+ }
1978
+
1979
+ [data-vgai-chrome="islands"] .vgai-chrome-island:not(:empty) {
1980
+ position: relative;
1981
+ border: var(--vgai-stroke-resting) solid
1982
+ var(--vgai-island-border-color, var(--vgai-boundary-strong));
1983
+ border-radius: var(--vgai-island-radius, var(--vgai-radius-md));
1984
+ background:
1985
+ var(--vgai-ambient-lift, none), var(--vgai-backdrop-dim, none),
1986
+ var(--vgai-island-surface, color-mix(in srgb, var(--vgai-surface-panel) 90%, transparent));
1987
+ box-shadow: var(--vgai-island-shadow, var(--vgai-shadow-sm));
1988
+ backdrop-filter: var(--vgai-island-backdrop-filter, blur(8px));
1989
+ -webkit-backdrop-filter: var(--vgai-island-backdrop-filter, blur(8px));
1990
+ text-shadow: var(--vgai-content-text-shadow, none);
1991
+ box-sizing: border-box;
1992
+ pointer-events: auto;
1993
+ }
1994
+ /* Same specular rim as .vgai-floating-toolbar::before. Keep decorative
1995
+ paint beneath interactive island children so it can never own a hit. */
1996
+ [data-vgai-chrome="islands"] .vgai-chrome-island:not(:empty)::before {
1997
+ content: "";
1998
+ position: absolute;
1999
+ inset: 0;
2000
+ z-index: 0;
2001
+ padding: 1px;
2002
+ border-radius: inherit;
2003
+ background: var(--vgai-island-specular-ring, none);
2004
+ -webkit-mask:
2005
+ linear-gradient(#000 0 0) content-box,
2006
+ linear-gradient(#000 0 0);
2007
+ -webkit-mask-composite: xor;
2008
+ mask:
2009
+ linear-gradient(#000 0 0) content-box exclude,
2010
+ linear-gradient(#000 0 0);
2011
+ pointer-events: none;
2012
+ }
2013
+ [data-vgai-chrome="islands"] .vgai-chrome-island:not(:empty) > * {
2014
+ position: relative;
2015
+ z-index: 1;
2016
+ }
2017
+ /* Size hierarchy (design-review item 2): small chrome must not wear
2018
+ big-chrome weight. Compact islands — the tray button and any other
2019
+ small single-control island — get a quieter rim, a smaller shadow, and
2020
+ a tighter radius than the full clusters/toolbars. */
2021
+ [data-vgai-chrome="islands"] .vgai-chrome-island[data-island-scale="compact"]:not(:empty) {
2022
+ border-radius: 12px;
2023
+ box-shadow: var(--vgai-shadow-sm);
2024
+ }
2025
+ [data-vgai-chrome="islands"] .vgai-chrome-island[data-island-scale="compact"]:not(:empty)::before {
2026
+ opacity: 0.55;
2027
+ }
2028
+ /* HUD islands (P6-U6): component-specific corrections the shared island
2029
+ material must not clobber. The camera pill is a READOUT — it must never
2030
+ start intercepting viewport pointers just because it became an island;
2031
+ the profiler chip's enabled state keeps its accent border signal. */
2032
+ [data-vgai-chrome="islands"] .vgai-camera-info.vgai-chrome-island:not(:empty),
2033
+ [data-vgai-chrome="islands"] .vgai-viewport-controls-hint.vgai-chrome-island:not(:empty) {
2034
+ pointer-events: none;
2035
+ }
2036
+ [data-vgai-chrome="islands"]
2037
+ .vgai-performance-overlay.vgai-chrome-island[data-enabled="true"]:not(:empty) {
2038
+ border-color: var(--vgai-accent);
2039
+ }
2040
+ /* Concentricity (owner design review, canonical HIG rule): highlights
2041
+ nested inside pill chrome follow the pill — capsule hover/active chips,
2042
+ not the flat design's small-radius rectangles. Var resolves to
2043
+ radius-full only for treatment themes; opaque themes keep radius-sm. */
2044
+ [data-vgai-chrome="islands"] .vgai-chrome-island .vgai-btn,
2045
+ .vgai-floating-toolbar .vgai-btn {
2046
+ border-radius: var(--vgai-island-control-radius, var(--vgai-radius-sm));
2047
+ }
2048
+ /* Singleton toolbar buttons ARE their own capsules (unrelated actions
2049
+ never share a glass tray — grouping is by relatedness, per the HIG). The
2050
+ one prominent action wears the tinted-glass capsule: accent as a
2051
+ translucent tint over the blur, never an opaque painted slab. */
2052
+ [data-vgai-chrome="islands"] .vgai-btn.vgai-chrome-island:not(:empty) {
2053
+ border-radius: var(--vgai-island-control-radius, var(--vgai-radius-sm));
2054
+ }
2055
+ [data-vgai-chrome="islands"] .vgai-btn.vgai-chrome-island[data-variant="primary"]:not(:empty) {
2056
+ background: var(--vgai-island-primary-bg, var(--vgai-accent));
2057
+ }
2058
+ .vgai-split-button {
2059
+ display: inline-flex;
2060
+ min-width: 0;
2061
+ align-items: stretch;
2062
+ }
2063
+ /* A SPLIT IS ONE WELL WITH ONE DIVIDER, and reaching the halves takes both
2064
+ shapes: `Tooltip` renders a `span.vgai-tooltip-anchor` AROUND its child, so
2065
+ `> .vgai-btn` silently matched nothing for every split whose halves carry
2066
+ tooltips — which is all of them but one. Measured 2026-09-18 through
2067
+ `editor.document.query` on the viewport header's snap group: the two halves
2068
+ each kept a full 1px border (2px of seam) and both kept their outer radii,
2069
+ where Blender's split shares ONE 1px #3c3c3c divider (native 2x
2070
+ `modeling.png` row y=62, x 1446..1447 between the magnet at #535353 and the
2071
+ snap dropdown at #272727). The tooltip anchor is `position: relative;
2072
+ display: inline-flex` and carries no paint of its own, so only the button
2073
+ inside it needs the rule. */
2074
+ .vgai-split-button > .vgai-btn:first-child,
2075
+ .vgai-split-button > :first-child > .vgai-btn {
2076
+ border-top-right-radius: 0;
2077
+ border-bottom-right-radius: 0;
2078
+ }
2079
+ .vgai-split-button > .vgai-btn + .vgai-btn,
2080
+ .vgai-split-button > * + .vgai-btn,
2081
+ .vgai-split-button > * + * > .vgai-btn {
2082
+ min-width: 20px;
2083
+ padding-inline: var(--vgai-space-1);
2084
+ border-left: 0;
2085
+ border-top-left-radius: 0;
2086
+ border-bottom-left-radius: 0;
2087
+ }
2088
+ .vgai-keycap {
2089
+ padding: 1px var(--vgai-space-2);
2090
+ color: var(--vgai-content-primary);
2091
+ background: var(--vgai-surface-raised);
2092
+ border: var(--vgai-stroke-resting) solid var(--vgai-boundary-default);
2093
+ border-radius: var(--vgai-radius-sm);
2094
+ font-family: var(--vgai-font-mono);
2095
+ font-size: var(--vgai-font-sm);
2096
+ }
2097
+ .vgai-viewport-toolbar {
2098
+ position: absolute;
2099
+ top: var(--vgai-viewport-overlay-top, var(--vgai-space-4));
2100
+ /* The full-canvas selection hit layer is z50. Keep viewport controls in
2101
+ the upper overlay band below menus (1000) but decisively above every
2102
+ canvas-local interaction surface, including nested stacking contexts. */
2103
+ z-index: calc(var(--vgai-z-dropdown, 1000) - 1);
2104
+ isolation: isolate;
2105
+ }
2106
+ .vgai-viewport-toolbar-left {
2107
+ left: var(--vgai-space-4);
2108
+ }
2109
+ .vgai-viewport-toolbar-right {
2110
+ right: var(--vgai-space-4);
2111
+ }
2112
+ /* U6.5 F1 — header-island clearance for viewport-overlay chrome. Under
2113
+ islands chrome over a BACKDROP document the header row is a
2114
+ position:absolute overlay (ProjectHeader.css full-bleed rule) and the
2115
+ dock runs edge-to-edge beneath it, so the viewport's own top-anchored
2116
+ chrome at viewport-local 8px sits exactly under the header islands band
2117
+ (y 8–46): the left toolstrip under the cluster island, the right
2118
+ view-controls under Export/Account — the stats toggle was 100%
2119
+ pointer-intercepted by the Account pill (the U6.5-FEED "Account ghost").
2120
+ Two inherited offsets move ALL of it below the 48px band instead:
2121
+ - `--vgai-viewport-overlay-top`: the toolbar rows' top — 48 + 8 = 56px.
2122
+ - `--vgai-viewport-overlay-hud-top`: the second HUD tier (stats /
2123
+ network readouts, StatsOverlay.tsx / NetworkInspectorPanel.tsx) — toolbar
2124
+ bottom (56 + 38) + the 8px breathing band (P2-18) = 102px.
2125
+ Under islands (glass) chrome the HUD tier gets the 8px breathing band:
2126
+ 54px = toolbar bottom 46 + 8px (P2-18 — it had decayed to 2px). A BACKDROP
2127
+ document pushes it further to 102px for the header-island clearance.
2128
+ Treatment-less "bars" chrome (graphite/classic/lite/reduced) leaves the var
2129
+ UNSET so the components' bare 48px fallback applies — pixel-identical to
2130
+ pre-glass main (the degrade-ladder invariant). */
2131
+ [data-vgai-chrome="islands"] {
2132
+ --vgai-viewport-overlay-hud-top: 54px;
2133
+ }
2134
+ [data-vgai-chrome="islands"] .vgai-editor-shell:has(.vgai-workspace-dock[data-vgai-backdrop]) {
2135
+ --vgai-viewport-overlay-top: 56px;
2136
+ --vgai-viewport-overlay-hud-top: 102px;
2137
+ }
2138
+ .vgai-viewport-popover-anchor {
2139
+ position: relative;
2140
+ display: inline-flex;
2141
+ align-items: center;
2142
+ }
2143
+ .vgai-snap-popover {
2144
+ position: absolute;
2145
+ top: calc(100% + var(--vgai-space-2));
2146
+ left: 0;
2147
+ z-index: var(--vgai-z-dropdown);
2148
+ width: 170px;
2149
+ padding: var(--vgai-space-4);
2150
+ }
2151
+ .vgai-helpers-menu {
2152
+ position: absolute;
2153
+ top: calc(100% + var(--vgai-space-2));
2154
+ right: 0;
2155
+ z-index: var(--vgai-z-dropdown);
2156
+ min-width: 140px;
2157
+ }
2158
+ .vgai-menu-check {
2159
+ display: inline-flex;
2160
+ width: 14px;
2161
+ flex: 0 0 auto;
2162
+ justify-content: center;
2163
+ margin-right: var(--vgai-space-2);
2164
+ }
2165
+ .vgai-selectable {
2166
+ transition:
2167
+ background-color 0.12s var(--vgai-ease-standard),
2168
+ border-color 0.12s var(--vgai-ease-standard),
2169
+ color 0.12s var(--vgai-ease-standard);
2170
+ }
2171
+ .vgai-selectable:hover {
2172
+ background: var(--vgai-neutral-hover);
2173
+ }
2174
+ .vgai-selectable[data-selected="true"] {
2175
+ background: var(--vgai-selection-bg);
2176
+ }
2177
+ .vgai-list-button {
2178
+ display: block;
2179
+ width: 100%;
2180
+ min-width: 0;
2181
+ border: 0;
2182
+ border-left: var(--vgai-stroke-active) solid transparent;
2183
+ background: transparent;
2184
+ color: var(--vgai-content-muted);
2185
+ padding: var(--vgai-space-3) var(--vgai-space-4);
2186
+ text-align: left;
2187
+ font: inherit;
2188
+ cursor: pointer;
2189
+ box-sizing: border-box;
2190
+ }
2191
+ .vgai-list-button:hover {
2192
+ color: var(--vgai-content-primary);
2193
+ background: var(--vgai-neutral-hover);
2194
+ }
2195
+ .vgai-list-button[data-selected="true"] {
2196
+ color: var(--vgai-content-primary);
2197
+ background: var(--vgai-selection-bg);
2198
+ }
2199
+ .vgai-list-button[data-selected="true"][data-accent="bar"] {
2200
+ border-left-color: var(--vgai-selection-indicator);
2201
+ }
2202
+ .vgai-list-button:disabled {
2203
+ opacity: 0.5;
2204
+ cursor: default;
2205
+ }
2206
+ .vgai-list-button:focus-visible {
2207
+ outline: var(--vgai-focus-ring);
2208
+ outline-offset: -2px;
2209
+ }
2210
+ .vgai-playbar {
2211
+ pointer-events: auto;
2212
+ }
2213
+ .vgai-play-edit-regime {
2214
+ display: inline-flex;
2215
+ width: var(--vgai-control-compact-height);
2216
+ height: var(--vgai-control-compact-height);
2217
+ align-items: center;
2218
+ justify-content: center;
2219
+ color: var(--vgai-warn);
2220
+ }
2221
+ .vgai-play-edit-regime[data-regime="overlay"] {
2222
+ color: var(--vgai-success);
2223
+ }
2224
+ .vgai-playbar-popover-anchor {
2225
+ position: relative;
2226
+ }
2227
+ .vgai-resolution-menu {
2228
+ z-index: var(--vgai-z-dropdown);
2229
+ min-width: 220px;
2230
+ }
2231
+ .vgai-resolution-menu [aria-checked="true"] {
2232
+ color: var(--vgai-content-primary);
2233
+ background: var(--vgai-selection-bg);
2234
+ }
2235
+ .vgai-camera-info,
2236
+ .vgai-viewport-controls-hint {
2237
+ position: absolute;
2238
+ /* Bottom-LEFT: the bottom-right corner belongs to the compact inspector
2239
+ card (owner, 2026-08-07 — the readout was sitting underneath it). */
2240
+ left: var(--vgai-space-4);
2241
+ bottom: var(--vgai-space-4);
2242
+ padding: var(--vgai-space-2) var(--vgai-space-3);
2243
+ border-radius: var(--vgai-radius-md);
2244
+ backdrop-filter: blur(8px);
2245
+ pointer-events: none;
2246
+ }
2247
+ .vgai-camera-preview {
2248
+ position: absolute;
2249
+ z-index: calc(var(--vgai-z-dropdown, 1000) - 2);
2250
+ left: var(--vgai-space-4);
2251
+ bottom: 52px;
2252
+ width: clamp(220px, 26vw, 360px);
2253
+ max-width: calc(100% - var(--vgai-space-8));
2254
+ overflow: visible;
2255
+ border-radius: var(--vgai-radius-md);
2256
+ background: transparent;
2257
+ box-shadow: var(--vgai-shadow-md);
2258
+ }
2259
+ .vgai-camera-preview-header {
2260
+ min-height: 28px;
2261
+ flex-wrap: nowrap;
2262
+ padding: 0 var(--vgai-space-2);
2263
+ border: var(--vgai-stroke-resting) solid var(--vgai-boundary-default);
2264
+ border-bottom: 0;
2265
+ border-radius: var(--vgai-radius-md) var(--vgai-radius-md) 0 0;
2266
+ background: var(--vgai-surface-overlay);
2267
+ backdrop-filter: blur(12px);
2268
+ }
2269
+ .vgai-camera-preview-title {
2270
+ min-width: 0;
2271
+ flex: 1;
2272
+ }
2273
+ .vgai-camera-preview-image {
2274
+ width: 100%;
2275
+ min-height: 120px;
2276
+ overflow: hidden;
2277
+ border: var(--vgai-stroke-resting) solid var(--vgai-boundary-default);
2278
+ border-top: 0;
2279
+ border-radius: 0 0 var(--vgai-radius-md) var(--vgai-radius-md);
2280
+ cursor: zoom-in;
2281
+ }
2282
+ .vgai-camera-preview-menu-anchor {
2283
+ position: relative;
2284
+ display: flex;
2285
+ }
2286
+ .vgai-camera-preview-menu {
2287
+ position: absolute;
2288
+ z-index: var(--vgai-z-dropdown);
2289
+ top: calc(100% + var(--vgai-space-1));
2290
+ right: 0;
2291
+ display: grid;
2292
+ width: max-content;
2293
+ min-width: 210px;
2294
+ padding: var(--vgai-space-1);
2295
+ }
2296
+ .vgai-camera-preview-menu .vgai-btn {
2297
+ justify-content: flex-start;
2298
+ }
2299
+ .vgai-camera-view-banner {
2300
+ position: absolute;
2301
+ z-index: calc(var(--vgai-z-dropdown, 1000) - 1);
2302
+ top: var(--vgai-viewport-overlay-top, var(--vgai-space-4));
2303
+ left: 50%;
2304
+ max-width: calc(100% - 520px);
2305
+ padding: var(--vgai-space-1) var(--vgai-space-2);
2306
+ border-radius: var(--vgai-radius-full);
2307
+ transform: translateX(-50%);
2308
+ backdrop-filter: blur(12px);
2309
+ }
2310
+ .vgai-camera-guides {
2311
+ position: absolute;
2312
+ z-index: calc(var(--vgai-z-sticky, 100) - 1);
2313
+ inset: 0;
2314
+ overflow: hidden;
2315
+ pointer-events: none;
2316
+ }
2317
+ .vgai-camera-guide {
2318
+ position: absolute;
2319
+ background: color-mix(in srgb, var(--vgai-content-primary) 40%, transparent);
2320
+ box-shadow: 0 0 1px color-mix(in srgb, var(--vgai-surface-shell) 70%, transparent);
2321
+ }
2322
+ .vgai-camera-guide-v1,
2323
+ .vgai-camera-guide-v2 {
2324
+ top: 0;
2325
+ bottom: 0;
2326
+ width: 1px;
2327
+ }
2328
+ .vgai-camera-guide-v1 {
2329
+ left: 33.333%;
2330
+ }
2331
+ .vgai-camera-guide-v2 {
2332
+ left: 66.666%;
2333
+ }
2334
+ .vgai-camera-guide-h1,
2335
+ .vgai-camera-guide-h2 {
2336
+ right: 0;
2337
+ left: 0;
2338
+ height: 1px;
2339
+ }
2340
+ .vgai-camera-guide-h1 {
2341
+ top: 33.333%;
2342
+ }
2343
+ .vgai-camera-guide-h2 {
2344
+ top: 66.666%;
2345
+ }
2346
+ .vgai-camera-safe-frame {
2347
+ position: absolute;
2348
+ inset: 6%;
2349
+ border: 1px solid color-mix(in srgb, var(--vgai-content-primary) 48%, transparent);
2350
+ }
2351
+ .vgai-camera-center-mark {
2352
+ position: absolute;
2353
+ top: 50%;
2354
+ left: 50%;
2355
+ width: 18px;
2356
+ height: 18px;
2357
+ transform: translate(-50%, -50%);
2358
+ }
2359
+ .vgai-camera-center-mark::before,
2360
+ .vgai-camera-center-mark::after {
2361
+ position: absolute;
2362
+ background: color-mix(in srgb, var(--vgai-content-primary) 60%, transparent);
2363
+ content: "";
2364
+ }
2365
+ .vgai-camera-center-mark::before {
2366
+ top: 8px;
2367
+ left: 0;
2368
+ width: 18px;
2369
+ height: 1px;
2370
+ }
2371
+ .vgai-camera-center-mark::after {
2372
+ top: 0;
2373
+ left: 8px;
2374
+ width: 1px;
2375
+ height: 18px;
2376
+ }
2377
+ @media (max-width: 900px) {
2378
+ .vgai-camera-view-banner {
2379
+ top: calc(var(--vgai-viewport-overlay-top, var(--vgai-space-4)) + 44px);
2380
+ max-width: calc(100% - var(--vgai-space-8));
2381
+ }
2382
+ }
2383
+ .vgai-react-canvas-controls {
2384
+ position: absolute;
2385
+ right: var(--vgai-space-6);
2386
+ bottom: var(--vgai-space-6);
2387
+ z-index: var(--vgai-z-sticky);
2388
+ overflow: visible;
2389
+ }
2390
+ .vgai-canvas-scene-navigation {
2391
+ position: absolute;
2392
+ top: calc(var(--vgai-viewport-overlay-top, var(--vgai-space-4)) + 44px);
2393
+ left: var(--vgai-space-4);
2394
+ z-index: calc(var(--vgai-z-dropdown, 1000) - 1);
2395
+ overflow: visible;
2396
+ }
2397
+ .vgai-react-viewport-select {
2398
+ border-color: transparent;
2399
+ background-color: transparent;
2400
+ }
2401
+
2402
+ .vgai-tabs {
2403
+ display: flex;
2404
+ height: var(--vgai-panel-header-height);
2405
+ min-width: 0;
2406
+ align-items: stretch;
2407
+ gap: var(--vgai-space-1);
2408
+ padding-inline: var(--vgai-space-2);
2409
+ overflow: hidden;
2410
+ background: var(--vgai-surface-sticky);
2411
+ border-bottom: var(--vgai-stroke-resting) solid var(--vgai-structural-divider);
2412
+ }
2413
+ .vgai-tab {
2414
+ position: relative;
2415
+ display: inline-flex;
2416
+ min-width: 0;
2417
+ max-width: 220px;
2418
+ align-items: center;
2419
+ gap: var(--vgai-space-2);
2420
+ padding: 0 var(--vgai-space-4);
2421
+ color: var(--vgai-content-muted);
2422
+ background: transparent;
2423
+ border: 0;
2424
+ font: inherit;
2425
+ cursor: pointer;
2426
+ }
2427
+ .vgai-tab:hover {
2428
+ color: var(--vgai-content-primary);
2429
+ background: var(--vgai-surface-raised);
2430
+ }
2431
+ .vgai-tab[data-selected="true"] {
2432
+ color: var(--vgai-content-primary);
2433
+ /* The active tab wears the panel it opens (Blender's tabs are filled
2434
+ slabs); the indicator below stays for the skins that read it. */
2435
+ background: var(--vgai-surface-panel);
2436
+ }
2437
+ .vgai-tab[data-selected="true"]::after {
2438
+ position: absolute;
2439
+ right: var(--vgai-space-4);
2440
+ bottom: 0;
2441
+ left: var(--vgai-space-4);
2442
+ height: var(--vgai-stroke-active);
2443
+ border-radius: 2px 2px 0 0;
2444
+ background: var(--vgai-selection-indicator);
2445
+ content: "";
2446
+ }
2447
+ .vgai-tab:focus-visible {
2448
+ outline: var(--vgai-focus-ring);
2449
+ outline-offset: -2px;
2450
+ }
2451
+ .vgai-tab-label {
2452
+ overflow: hidden;
2453
+ text-overflow: ellipsis;
2454
+ white-space: nowrap;
2455
+ }
2456
+ .vgai-tab-dirty {
2457
+ width: 6px;
2458
+ height: 6px;
2459
+ flex: 0 0 auto;
2460
+ border-radius: var(--vgai-radius-full);
2461
+ background: var(--vgai-warn);
2462
+ }
2463
+ .vgai-tab-badge {
2464
+ display: inline-flex;
2465
+ width: max-content;
2466
+ max-width: 100%;
2467
+ min-width: 15px;
2468
+ height: 15px;
2469
+ align-items: center;
2470
+ justify-content: center;
2471
+ /* space-2, not space-1: with true half-circle end caps (radius-full is
2472
+ * 9999px, no longer 50%) multi-char content needs breathing room past the
2473
+ * cap curve; border-box + min-width keeps a single digit a perfect circle. */
2474
+ padding: 0 var(--vgai-space-2);
2475
+ border-radius: var(--vgai-radius-full);
2476
+ color: var(--vgai-surface-shell);
2477
+ background: var(--vgai-content-muted);
2478
+ font-size: var(--vgai-font-xs);
2479
+ line-height: var(--vgai-leading-tight);
2480
+ box-sizing: border-box;
2481
+ }
2482
+
2483
+ /* A badge is a compact LABEL by default. The fixed radius and intrinsic,
2484
+ * non-flexing box make multiword content safe: it cannot turn into the long
2485
+ * elliptical capsule produced by border-radius: 50% on a wide rectangle, nor
2486
+ * stretch to fill a grid/flex track. True counters opt into data-shape=count. */
2487
+ .vgai-badge {
2488
+ display: inline-flex;
2489
+ flex: 0 0 auto;
2490
+ align-self: center;
2491
+ width: fit-content;
2492
+ max-width: 100%;
2493
+ min-width: 0;
2494
+ min-height: 18px;
2495
+ align-items: center;
2496
+ justify-content: center;
2497
+ padding: 1px var(--vgai-space-2);
2498
+ overflow: hidden;
2499
+ border-radius: var(--vgai-radius-sm);
2500
+ color: var(--vgai-surface-shell);
2501
+ background: var(--vgai-content-muted);
2502
+ font-size: var(--vgai-font-xs);
2503
+ line-height: var(--vgai-leading-tight);
2504
+ white-space: nowrap;
2505
+ text-overflow: ellipsis;
2506
+ vertical-align: middle;
2507
+ box-sizing: border-box;
2508
+ }
2509
+
2510
+ .vgai-badge[data-shape="count"] {
2511
+ min-width: 15px;
2512
+ min-height: 15px;
2513
+ height: 15px;
2514
+ /* space-2, not space-1: at 2–3 digits the 9999px end-caps eat into a
2515
+ * 2px pad and the glyphs graze the curve (reviewer finding, U0). */
2516
+ padding: 0 var(--vgai-space-2);
2517
+ border-radius: var(--vgai-radius-full);
2518
+ }
2519
+
2520
+ .vgai-tree {
2521
+ min-width: 0;
2522
+ color: var(--vgai-content-primary);
2523
+ }
2524
+ .vgai-tree-row {
2525
+ display: flex;
2526
+ height: var(--vgai-tree-row-height);
2527
+ min-width: 0;
2528
+ align-items: center;
2529
+ gap: var(--vgai-space-2);
2530
+ padding-right: var(--vgai-space-2);
2531
+ color: var(--vgai-content-muted);
2532
+ border-left: var(--vgai-stroke-active) solid transparent;
2533
+ box-sizing: border-box;
2534
+ cursor: default;
2535
+ user-select: none;
2536
+ }
2537
+ /* Blender's outliner alternates rows by a few levels (a three-level step),
2538
+ phased on the row's ABSOLUTE index — `nth-child` counted this virtualized
2539
+ list's leading spacer div as sibling one, so row 0 always drew the alternate
2540
+ and the whole stripe flipped with the scroll window. */
2541
+ .vgai-tree-row[data-row-alt] {
2542
+ background: var(--vgai-surface-row-alt);
2543
+ }
2544
+ /* THE STRIPE BELONGS TO THE BODY, NOT TO THE ROWS — and that one rule is the
2545
+ largest area of the whole frame that differed from Blender.
2546
+
2547
+ MEASURED, `modeling-object-none.png` at native 2x (device y, column
2548
+ x=3300): the Outliner body starts at 100 and Blender stripes it to the
2549
+ bottom edge at a 40 px (20 CSS) pitch — 106..145 #272727, 146..185
2550
+ #2a2a2a, 186..225, 226..265, 266..305 for the five occupied rows, and then
2551
+ 306..345 #2a2a2a, 346..385 #272727, 386..409 #2a2a2a with NOTHING IN THEM.
2552
+ Ours painted the occupied rows and stopped: flat 39 for 320 device px,
2553
+ which is why Blender's near-empty Outliner reads as a LIST and ours read
2554
+ as an empty dark box.
2555
+
2556
+ PHASE, measured rather than assumed: the ROOT row (Scene Collection,
2557
+ 106..145) takes the panel value and the second row takes the alternate, so
2558
+ the body's parity is exactly the `rowIndex % 2 === 1` the occupied rows
2559
+ already use. The two painters therefore agree by construction; an occupied
2560
+ alternate row paints its own identical fill over this gradient, which is
2561
+ what keeps a region-substituted `--vgai-surface-row-alt` (the workspace host
2562
+ re-emits it per region) correct on both.
2563
+
2564
+ `background-attachment: local` is the whole trick, and it is what makes
2565
+ this the CONTENT's stripe rather than the viewport's: the gradient's origin
2566
+ scrolls with the rows, so the phase holds at any scroll offset and the
2567
+ virtualization spacers need no arithmetic of their own.
2568
+
2569
+ It paints UNDER every row, so the selection band, the active plate and
2570
+ hover all keep winning — each of those is a fill on the row itself.
2571
+
2572
+ SCOPED TO A PAINTED OUTLINER, and that scope is the whole of the gate. The
2573
+ stripe is a shape a reference frame justifies — Blender's Outliner reads as
2574
+ a LIST because it is striped to the bottom edge — so it belongs to the look
2575
+ that measured it and not to a host with none declared (ARCHITECTURE-CORE
2576
+ §The core is the workbench, "Host default versus skew"; Classic's frame is
2577
+ "no zebra"). The condition is the AREA, not a look: `data-vgai-region` is
2578
+ published on the tree body by the panel that IS the Outliner, and only
2579
+ while the installed palette names `color.region.outliner`
2580
+ (`components/GameHierarchy.tsx`, `theme-preference.ts`'s
2581
+ `editorPaintedRegions`). So any palette that paints editor areas gets the
2582
+ stripe and no rule here ever learns a palette's name. The ink stays
2583
+ `--vgai-surface-row-alt`, which every palette derives, so there is still no
2584
+ new theme member and no flag. */
2585
+ .vgai-tree-body[data-vgai-region="outliner"] {
2586
+ background-image: repeating-linear-gradient(
2587
+ to bottom,
2588
+ transparent 0,
2589
+ transparent var(--vgai-tree-row-height),
2590
+ var(--vgai-surface-row-alt) var(--vgai-tree-row-height),
2591
+ var(--vgai-surface-row-alt) calc(var(--vgai-tree-row-height) * 2)
2592
+ );
2593
+ background-attachment: local;
2594
+ }
2595
+ .vgai-tree-row:hover {
2596
+ color: var(--vgai-content-primary);
2597
+ background: var(--vgai-neutral-hover);
2598
+ }
2599
+ /* THE SELECTION BAND, and why it is a background LAYER rather than a fill.
2600
+ The canonical selected treatment is the FILL alone (the viewport menus'
2601
+ aria-checked rows, .vgai-selectable, the chat rows). The left accent bar this
2602
+ once added read as a stray one-sided border (owner, 2026-08-07). The fill is
2603
+ the TREE's own band, not `--vgai-selection-bg`: a selected Outliner row is a
2604
+ darker navy than the accent that variable resolves to, which Blender keeps
2605
+ for pressed widgets.
2606
+
2607
+ Measured in `modeling-object-selected.png` (Blender 5.2, object mode, every
2608
+ object selected; the values and the derivations are on
2609
+ `--vgai-tree-row-selected-bg` in theme.ts): Blender's band is 18 of the row's
2610
+ 20 px, so ONE row of the ordinary #272727/#2a2a2a stripe shows above and
2611
+ below every selected row. Ours filled the whole 20, and the difference is not
2612
+ subtle at a multi-selection: eleven adjacent selected rows painted ONE
2613
+ continuous slab across the panel with no row boundary anywhere in it, where
2614
+ Blender paints eleven separated bars.
2615
+
2616
+ So the band is a background IMAGE, positioned and sized to the inset, and
2617
+ never a box: the row's height IS the Outliner's 20 px pitch (`chrome.treeIndent`'s
2618
+ sibling, and what `treeRowHeight` measures for hit-testing), so a border or a
2619
+ padding that produced the same picture would move every row. The positioning
2620
+ area is the PADDING box, which already starts after the row's transparent
2621
+ `border-left`, so `0` on the x axis lands 2 px in — Blender's own left inset
2622
+ (band at device x 7 against content from x 2). The right inset is 0 in the
2623
+ frame and 0 here. Blender rounds the box by 1 px; at that radius it is one
2624
+ corner pixel and it is deliberately not drawn. */
2625
+ .vgai-tree-row[data-selected="true"] {
2626
+ color: var(--vgai-content-primary);
2627
+ background-image: linear-gradient(
2628
+ var(--vgai-tree-row-selected-bg),
2629
+ var(--vgai-tree-row-selected-bg)
2630
+ );
2631
+ background-repeat: no-repeat;
2632
+ background-position: 0 1px;
2633
+ background-size: 100% calc(100% - 2px);
2634
+ }
2635
+ /* THE ACTIVE ROW — Blender's second selected state, and the one fact this
2636
+ editor already acts on without ever showing it. `selectedEntityId` (the
2637
+ store's `[...selection].at(-1)`, the insertion-ordered last) is what the
2638
+ gizmo binds to and what the inspector inspects; within a multi-selection
2639
+ nothing on screen said WHICH row that was. Blender's answer is its active
2640
+ object: the lighter band inside a hairline, against the dim band on every
2641
+ other selected row.
2642
+
2643
+ Two background layers make the hairline: the border colour sized to the whole
2644
+ inset box, the fill sized 1 px smaller on each side and offset to match. A
2645
+ `box-shadow: inset` would paint the ring on the BORDER box — the full 20 px —
2646
+ and miss the inset entirely. */
2647
+ .vgai-tree-row[data-selection-active="true"] {
2648
+ background-image:
2649
+ linear-gradient(var(--vgai-tree-row-active-bg), var(--vgai-tree-row-active-bg)),
2650
+ linear-gradient(var(--vgai-tree-row-active-border), var(--vgai-tree-row-active-border));
2651
+ background-position:
2652
+ 1px 2px,
2653
+ 0 1px;
2654
+ background-size:
2655
+ calc(100% - 2px) calc(100% - 4px),
2656
+ 100% calc(100% - 2px);
2657
+ }
2658
+ .vgai-tree-row[data-muted="true"] {
2659
+ color: var(--vgai-content-dim);
2660
+ }
2661
+ .vgai-tree-row[data-dragging="true"] {
2662
+ color: var(--vgai-content-muted);
2663
+ background: var(--vgai-neutral-hover);
2664
+ outline: 1px dashed var(--vgai-boundary-strong);
2665
+ outline-offset: -1px;
2666
+ }
2667
+ /* THE INDENT GUIDE — the vertical rule an OPEN parent draws down its own
2668
+ disclosure column, through every row of its subtree. One span per ancestor
2669
+ column, absolutely positioned against the row's PADDING box (which starts at
2670
+ the same x on every row, before that row's own `padding-left`), so column k
2671
+ lands on the depth-k caret's centre for every descendant alike.
2672
+
2673
+ MEASURED on `modeling-object-none.png` / `modeling-object-selected.png`
2674
+ (Blender 5.2, native 2x, OBJECT mode; the Outliner crop is
2675
+ `modeling.png[y 53..419, x 2843..3452]` = `outliner.png`'s own box):
2676
+
2677
+ - ONE device pixel at x 59 — CSS 29.5..30.0 — against the Collection
2678
+ row's chevron centre of 30.25. Hence `width: 0.5px` placed half a pixel
2679
+ left of the column centre, which lands on Blender's own two device
2680
+ pixels at 2x; at 1x the browser antialiases it, which is the honest
2681
+ rendering of a one-device-pixel rule.
2682
+ - y 143..242 CONTINUOUS — 5 CSS px into the first child's row, 5 CSS px
2683
+ above the last child's row bottom (rows are 40 device px: Camera
2684
+ 133..172, Cube 173..212, Light 213..252). Those two 5s are the `top`
2685
+ and `bottom` the row applies at the ENDS of a block only.
2686
+ - every one of those 100 pixels is exactly (101,101,101) over three
2687
+ different backdrops — the #272727/#2a2a2a stripe, the #1c304c selected
2688
+ bar and the #324c7f active bar — so the ink is OPAQUE and painted OVER
2689
+ the row's fill. That is why this is an element above the background and
2690
+ not another `background-image` layer: the selected and active rules
2691
+ already own `background-image` outright and would drop it.
2692
+
2693
+ WHERE THE EVIDENCE STOPS. The frame nests exactly ONE level, and its root
2694
+ row (Scene Collection) carries no chevron and no guide under it — x 0..48 is
2695
+ clear for the whole Collection row, measured. So "one rule per open
2696
+ ancestor" is EXTRAPOLATION beyond the first level; it is the reading taken
2697
+ because the alternative the frame equally supports — a rule under the
2698
+ IMMEDIATE parent only — breaks the very continuity the frame does show: a
2699
+ grandparent's rule would stop at its grandchildren and resume after them.
2700
+ The geometry is not a density member for the same reason `content.active`
2701
+ is not: a palette that names no `color.boundary.indent` draws no guide at
2702
+ all, so there is nothing for another skin to retune. */
2703
+ .vgai-tree-indent-guide {
2704
+ position: absolute;
2705
+ width: 0.5px;
2706
+ background: var(--vgai-tree-indent-guide);
2707
+ pointer-events: none;
2708
+ }
2709
+ .vgai-tree-remote-selection {
2710
+ display: inline-flex;
2711
+ flex: 0 0 auto;
2712
+ align-items: center;
2713
+ }
2714
+ .vgai-tree-remote-selection > span {
2715
+ width: 7px;
2716
+ height: 7px;
2717
+ margin-left: -2px;
2718
+ border: 1px solid var(--vgai-surface-panel);
2719
+ border-radius: var(--vgai-radius-full);
2720
+ }
2721
+ .vgai-tree-row:focus-visible {
2722
+ outline: var(--vgai-focus-ring);
2723
+ outline-offset: -2px;
2724
+ }
2725
+ /* THE GLYPH PLATE — ONE WIDGET, TWO SITES. Blender draws the same rounded
2726
+ plate behind the ACTIVE row's type glyph and behind the datablock glyph of
2727
+ the data it is editing, so the box lives here once and each site paints its
2728
+ own fill into it.
2729
+
2730
+ MEASURED at native 2x on the Cube row, and the three frames AGREE to the
2731
+ pixel: `modeling-object-none.png` (x 2964..3003, y 228..263 of the frame;
2732
+ x 121..160, y 175..210 of the `[x2843..3452, y53..419]` Outliner crop),
2733
+ `modeling-object-selected.png` and `outliner.png` all put it at 40x36
2734
+ device px — 20x18 CSS, one indent cell wide and one CSS px of stripe above
2735
+ and below, exactly the selection bar's own gutters — with a 10 device-px
2736
+ (5 CSS) corner: the outer edge is first full at y=184 on a box whose top is
2737
+ y=175, and the top edge is first full at x=131 on a box whose left is
2738
+ x=121. FIVE is not `radius.sm`'s four, and the one CSS px shows at 6x on a
2739
+ 20-px box; it is this plate's own number and it has no token. */
2740
+ .vgai-tree-row {
2741
+ --vgai-tree-glyph-plate-width: var(--vgai-tree-indent);
2742
+ --vgai-tree-glyph-plate-height: calc(var(--vgai-tree-row-height) - var(--vgai-space-1));
2743
+ --vgai-tree-glyph-plate-radius: 5px;
2744
+ }
2745
+ /* THE ACTIVE OBJECT'S PLATE, and WHICH FACT IT MARKS. Blender puts this plate
2746
+ behind the type glyph of the ACTIVE object, and it is INDEPENDENT of
2747
+ selection: `modeling-object-none.png` has nothing selected at all and the
2748
+ Cube's glyph still wears it; `modeling-object-selected.png` has all three
2749
+ rows selected and only the Cube's — the active one — wears it. (The record
2750
+ this unit inherited said the plate marks the active object's DATA over
2751
+ selection state. It does not: in BOTH object-mode frames the Cube's mesh
2752
+ glyph carries NO plate while its OBJECT glyph does. What is absent from the
2753
+ object-mode frames is the DATA plate below, which is edit-mode's.)
2754
+
2755
+ Where Blender has an active object with nothing selected this editor has no
2756
+ active id at all, so the plate simply does not paint — the store's anchor is
2757
+ the only honest answer to "which row is active" and inventing a second one
2758
+ is a noun this editor does not have.
2759
+
2760
+ THE INK IS THE ROW'S OWN, AT TWO ALPHAS, and that is a fit rather than a
2761
+ taste call: over the stripe's #2a2a2a the plate measures #525252 fill /
2762
+ #686868 border, and over the active row's #324c7f it measures (87,107,144)
2763
+ fill / (108,124,154) border. Solving both backgrounds at once returns one
2764
+ ink of 190-194 at 26% and 41% — and `content.muted` (#c2c2c2, the ink the
2765
+ unselected row's own NAME already takes) reproduces every channel of both
2766
+ frames within one level. No new palette member: the plate is the row's text
2767
+ colour, thinned.
2768
+
2769
+ THE BORDER'S DECLARED ALPHA IS 20.3%, NOT THE MEASURED 41%, and the
2770
+ difference is the fill already lying under it: `background-clip` is
2771
+ `border-box`, so the border composites over the plate rather than over the
2772
+ row — the same model its datablock sibling below records. Solving
2773
+ (1-b)(1-0.26) = 1-0.41 gives b = 0.203, which puts the painted column back
2774
+ at 41% of the ink over the row. Measured before the correction: the border
2775
+ landed at an effective 56.5% and read as a bright hairline. */
2776
+ .vgai-tree-type-glyph {
2777
+ position: relative;
2778
+ z-index: 0;
2779
+ }
2780
+ /* The site's half of the category ink, on the GLYPH and not on the cell —
2781
+ the same split `.vgai-tree-datablock > *` makes, and for the same reason:
2782
+ the plate's fill and border are measured as PAINTED, and a group opacity
2783
+ on the cell would put 0.80 through them a second time. */
2784
+ .vgai-tree-type-glyph > * {
2785
+ opacity: 0.8;
2786
+ }
2787
+ .vgai-tree-row[data-selection-active="true"] .vgai-tree-type-glyph::before {
2788
+ content: "";
2789
+ position: absolute;
2790
+ z-index: -1;
2791
+ top: 50%;
2792
+ left: 50%;
2793
+ box-sizing: border-box;
2794
+ width: var(--vgai-tree-glyph-plate-width);
2795
+ height: var(--vgai-tree-glyph-plate-height);
2796
+ transform: translate(-50%, -50%);
2797
+ border: var(--vgai-stroke-resting) solid
2798
+ color-mix(in srgb, var(--vgai-content-muted) 20.3%, transparent);
2799
+ border-radius: var(--vgai-tree-glyph-plate-radius);
2800
+ background: color-mix(in srgb, var(--vgai-content-muted) 26%, transparent);
2801
+ pointer-events: none;
2802
+ }
2803
+ /* THE COLLAPSED ROW'S DATABLOCK GLYPH — Blender's rest state (the summary a
2804
+ `>` row draws for the datablock it is hiding; `GameHierarchy.tsx`'s `Row`
2805
+ carries the geometry's coordinates). One indent cell wide, two points
2806
+ shorter than the row, and the gap before it is one cell.
2807
+
2808
+ THE PLATE, and exactly which state earns it. In `outliner.png` the CUBE —
2809
+ the active object, the one Blender draws with an orange name — is the row
2810
+ whose datablock glyph sits on a rounded plate; the Light's, on an ordinary
2811
+ row, has none. This one is EDIT MODE's: it is absent from both object-mode
2812
+ frames, where the same Cube row's mesh glyph is bare, and the Model
2813
+ document — whose viewport header reads Edit Mode — is that frame's sibling.
2814
+ (The CAMERA's datablock carries a NEUTRAL grey plate in all three frames,
2815
+ `#4f4f4f` fill / `#666666` border over the row's `#272727` — the same box
2816
+ the active object's type glyph wears above, in the neutral ink rather than
2817
+ the data green. That one marks "the scene's active camera", a fact this
2818
+ editor has no analogue for and no way to answer honestly, so it is measured
2819
+ here and deliberately not drawn.)
2820
+ Ours takes the ACTIVE row, which is what "active object" means in this
2821
+ editor — see the gate's own comment in `GameHierarchy.tsx`.
2822
+
2823
+ THE TINT IS TRANSCRIBED, NOT MODELLED. Blender's plate under the Cube's
2824
+ mesh measures `#1f4038` fill with a `#506761` border over the alternate
2825
+ row's `#2a2a2a` (src x 263-302, y 175-210), and no single alpha of the data
2826
+ ink reproduces it — the fill's red falls 11 below the row where the ink's
2827
+ own red is already clamped at zero. What DOES reproduce it, every channel
2828
+ exactly, is the ink darkened to 60% and laid on at 26%: 42*0.74 = 31,
2829
+ 42+0.26*(126.6-42) = 64, 42+0.26*(97.2-42) = 56.
2830
+
2831
+ THE BORDER CARRIES NO INK, and that is a solve rather than a simplification.
2832
+ Fitting a green-tinted light term to the frame's `#506761` returns a
2833
+ NEGATIVE ink weight, so the hue in that hairline is entirely the fill
2834
+ showing through: `content.primary` at 24% over the fill lands (79,104,98)
2835
+ against the frame's (80,103,97), one level anywhere. It composites over the
2836
+ PLATE, not the row — measured, not assumed: a temporary opaque border on the
2837
+ live surface showed the painted column is the border colour over the
2838
+ element's own background at exactly its declared alpha (the earlier
2839
+ 41%-over-fill cut predicted (88,146,156) over our selection blue and the
2840
+ frame delivered (88,146,156)).
2841
+
2842
+ Both terms composite through `transparent`, so they still read over the
2843
+ selected row's own fill — which Blender does not paint at all: its outliner
2844
+ marks selection on the NAME (the Cube's label is orange) and leaves the row
2845
+ its ordinary stripe, so over our blue selection the plate reads as a teal
2846
+ chip rather than the frame's dark green. That difference is the row FILL's,
2847
+ not this plate's, and closing it is the selected row's own unit. */
2848
+ .vgai-tree-datablock {
2849
+ display: inline-flex;
2850
+ align-items: center;
2851
+ justify-content: center;
2852
+ box-sizing: border-box;
2853
+ flex-shrink: 0;
2854
+ vertical-align: middle;
2855
+ width: var(--vgai-tree-glyph-plate-width);
2856
+ height: var(--vgai-tree-glyph-plate-height);
2857
+ margin-left: var(--vgai-tree-indent);
2858
+ font-size: var(--vgai-icon-sm);
2859
+ border: var(--vgai-stroke-resting) solid transparent;
2860
+ /* The SAME corner as the active object's plate above — one widget, one
2861
+ number. It read `radius.sm` (4) against the frame's measured 5. */
2862
+ border-radius: var(--vgai-tree-glyph-plate-radius);
2863
+ }
2864
+ /* The site's half of the category ink — the Outliner composites its GLYPHS at
2865
+ 0.80, the same measurement the row's own type glyph applies. On the glyph
2866
+ and not on the cell: the plate's own fill and border are measured as
2867
+ painted, and dimming the whole cell would put 0.80 through them twice. */
2868
+ .vgai-tree-datablock > * {
2869
+ opacity: 0.8;
2870
+ }
2871
+ .vgai-tree-datablock[data-active="true"] {
2872
+ background: color-mix(
2873
+ in srgb,
2874
+ color-mix(in srgb, var(--vgai-category-data, currentColor) 60%, black) 26%,
2875
+ transparent
2876
+ );
2877
+ border-color: color-mix(in srgb, var(--vgai-content-primary) 24%, transparent);
2878
+ }
2879
+ .vgai-tree-row-leading {
2880
+ display: inline-flex;
2881
+ width: 14px;
2882
+ flex: 0 0 auto;
2883
+ justify-content: center;
2884
+ }
2885
+ .vgai-tree-row-label {
2886
+ min-width: 0;
2887
+ flex: 1;
2888
+ overflow: hidden;
2889
+ text-overflow: ellipsis;
2890
+ white-space: nowrap;
2891
+ }
2892
+ .vgai-tree-row-actions {
2893
+ display: inline-flex;
2894
+ flex: 0 0 auto;
2895
+ align-items: center;
2896
+ gap: var(--vgai-space-1);
2897
+ }
2898
+ /* A ROW'S TOGGLES ARE BRIGHTER THAN ITS LABEL. Measured in `outliner.png`:
2899
+ the eye and camera toggles ink at #e5e5e5 (src x 510–586, y 98–172) while
2900
+ the row's own text is #c2c2c2 (src x 166–276) — which under the Blender
2901
+ palette are exactly `content.primary` and `content.muted`. A ghost
2902
+ IconButton reads `--vgai-content-muted`, i.e. the label's ink, so inside a
2903
+ tree row the toggles were as quiet as the name they act on. They are
2904
+ controls: the brighter token is `--vgai-content-primary`. Scoped to the
2905
+ row (both shapes — `patterns/Tree.tsx`'s actions slot and the flat rows
2906
+ `GameHierarchy.tsx` builds), never to ghost buttons at large. */
2907
+ .vgai-tree-row .vgai-btn[data-variant="ghost"]:not(.vgai-tree-caret) {
2908
+ color: var(--vgai-content-primary);
2909
+ }
2910
+ /* ...but the EXPANDER is not one of them. Same frame, same scale: the
2911
+ Collection row's open chevron inks #c2c2c2 (src x 52-69, y 110-119) — the
2912
+ row's own label ink, not the toggles' #e5e5e5. It is a disclosure mark on
2913
+ the name, not a control acting on the object, and the rule above had swept
2914
+ it up with the eye. */
2915
+ .vgai-tree-row .vgai-btn.vgai-tree-caret {
2916
+ color: var(--vgai-content-muted);
2917
+ }
2918
+
2919
+ .vgai-field-group {
2920
+ display: grid;
2921
+ gap: var(--vgai-space-3);
2922
+ padding: var(--vgai-space-4);
2923
+ }
2924
+ .vgai-field-row {
2925
+ display: grid;
2926
+ grid-template-columns: minmax(64px, 84px) minmax(0, 1fr);
2927
+ min-width: 0;
2928
+ align-items: center;
2929
+ gap: var(--vgai-space-3);
2930
+ }
2931
+ .vgai-field-row[data-compact="true"] {
2932
+ grid-template-columns: minmax(52px, 72px) minmax(0, 1fr);
2933
+ }
2934
+ .vgai-field-label {
2935
+ min-width: 0;
2936
+ color: var(--vgai-content-muted);
2937
+ font-size: var(--vgai-font-base);
2938
+ }
2939
+ .vgai-field-control {
2940
+ display: flex;
2941
+ min-width: 0;
2942
+ align-items: center;
2943
+ gap: var(--vgai-space-2);
2944
+ }
2945
+ .vgai-field-hint {
2946
+ grid-column: 2;
2947
+ color: var(--vgai-content-dim);
2948
+ font-size: var(--vgai-font-sm);
2949
+ line-height: 1.35;
2950
+ }
2951
+
2952
+ .vgai-state-surface {
2953
+ display: flex;
2954
+ min-width: 0;
2955
+ min-height: 120px;
2956
+ flex-direction: column;
2957
+ align-items: center;
2958
+ justify-content: center;
2959
+ gap: var(--vgai-space-3);
2960
+ padding: var(--vgai-space-8);
2961
+ color: var(--vgai-content-muted);
2962
+ text-align: center;
2963
+ box-sizing: border-box;
2964
+ }
2965
+ .vgai-state-surface[data-compact="true"] {
2966
+ min-height: 64px;
2967
+ padding: var(--vgai-space-4);
2968
+ }
2969
+ .vgai-state-surface[data-tone="error"] {
2970
+ color: var(--vgai-danger);
2971
+ }
2972
+ .vgai-state-surface[data-tone="success"] {
2973
+ color: var(--vgai-success);
2974
+ }
2975
+ .vgai-state-surface[data-tone="loading"] .vgai-state-surface-icon {
2976
+ animation: vgai-editor-spin 0.8s linear infinite;
2977
+ }
2978
+ .vgai-state-surface-icon {
2979
+ color: currentColor;
2980
+ font-size: var(--vgai-font-2xl);
2981
+ }
2982
+ .vgai-state-surface-title {
2983
+ color: var(--vgai-content-primary);
2984
+ font-size: var(--vgai-font-md);
2985
+ }
2986
+ .vgai-state-surface-description {
2987
+ max-width: 42ch;
2988
+ color: var(--vgai-content-dim);
2989
+ line-height: var(--vgai-leading-normal);
2990
+ }
2991
+ .vgai-state-surface-action {
2992
+ margin-top: var(--vgai-space-2);
2993
+ }
2994
+
2995
+ .vgai-banner {
2996
+ /* U6a: the tone tint is a custom property so the islands-gated glass rule
2997
+ * below can LAYER it over the island material instead of repeating the
2998
+ * four tone selectors. Inert for the base (bars) paint — the background
2999
+ * consumes it directly, byte-identical to the former literal. */
3000
+ --vgai-banner-tone-tint: var(--vgai-accent-muted);
3001
+ display: flex;
3002
+ min-width: 0;
3003
+ align-items: center;
3004
+ gap: var(--vgai-space-3);
3005
+ padding: var(--vgai-space-3) var(--vgai-space-5);
3006
+ border-bottom: var(--vgai-stroke-resting) solid var(--vgai-accent);
3007
+ color: var(--vgai-accent);
3008
+ background: var(--vgai-banner-tone-tint);
3009
+ box-sizing: border-box;
3010
+ }
3011
+ .vgai-banner[data-tone="warning"] {
3012
+ --vgai-banner-tone-tint: var(--vgai-warn-muted);
3013
+ color: var(--vgai-warn);
3014
+ border-color: var(--vgai-warn);
3015
+ }
3016
+ .vgai-banner[data-tone="error"] {
3017
+ --vgai-banner-tone-tint: var(--vgai-danger-faint);
3018
+ color: var(--vgai-danger);
3019
+ border-color: var(--vgai-danger);
3020
+ }
3021
+ .vgai-banner[data-tone="success"] {
3022
+ --vgai-banner-tone-tint: var(--vgai-success-muted);
3023
+ color: var(--vgai-success);
3024
+ border-color: var(--vgai-success);
3025
+ }
3026
+ /* U6a (P6 glass-native chrome, inventory row "scene banners"): under islands
3027
+ * chrome — treatment presence, never theme identity — a banner is glass: the
3028
+ * island material (surface + plain backdrop-filter list) with the SEMANTIC
3029
+ * TINT layered on top of it, so the tone still reads while the scene shows
3030
+ * through frosted. A flat blur, never a second refraction lens — banners
3031
+ * paint inside documents/cards, and the one-lens-per-card rule holds. Bars
3032
+ * chrome (graphite/classic and every reduced-transparency/lite fallback)
3033
+ * never matches this rule and keeps the flat tint byte-identically. */
3034
+ [data-vgai-chrome="islands"] .vgai-banner {
3035
+ background:
3036
+ linear-gradient(var(--vgai-banner-tone-tint), var(--vgai-banner-tone-tint)),
3037
+ var(--vgai-island-surface, transparent);
3038
+ backdrop-filter: var(--vgai-island-backdrop-filter, none);
3039
+ -webkit-backdrop-filter: var(--vgai-island-backdrop-filter, none);
3040
+ text-shadow: var(--vgai-content-text-shadow, none);
3041
+ }
3042
+ .vgai-banner-icon {
3043
+ display: inline-flex;
3044
+ flex: 0 0 auto;
3045
+ }
3046
+ .vgai-banner-content {
3047
+ min-width: 0;
3048
+ flex: 1;
3049
+ color: var(--vgai-content-primary);
3050
+ }
3051
+ .vgai-banner-actions {
3052
+ display: flex;
3053
+ flex: 0 0 auto;
3054
+ align-items: center;
3055
+ gap: var(--vgai-space-2);
3056
+ }
3057
+
3058
+ .vgai-popover {
3059
+ color: var(--vgai-content-primary);
3060
+ background: var(--vgai-glass-regular-bg, var(--vgai-surface-chrome));
3061
+ border: var(--vgai-stroke-resting) solid var(--vgai-boundary-strong);
3062
+ border-radius: var(--vgai-radius-md);
3063
+ box-shadow: var(--vgai-shadow-md);
3064
+ }
3065
+ .vgai-drop-indicator {
3066
+ position: absolute;
3067
+ z-index: var(--vgai-z-overlay-low);
3068
+ pointer-events: none;
3069
+ border: var(--vgai-stroke-active) solid var(--vgai-selection-border);
3070
+ background: var(--vgai-selection-bg);
3071
+ box-sizing: border-box;
3072
+ }
3073
+ .vgai-drop-indicator[data-position="before"] {
3074
+ inset: 0 0 auto;
3075
+ height: var(--vgai-stroke-active);
3076
+ }
3077
+ .vgai-drop-indicator[data-position="after"] {
3078
+ inset: auto 0 0;
3079
+ height: var(--vgai-stroke-active);
3080
+ }
3081
+ .vgai-drop-indicator[data-position="inside"] {
3082
+ inset: 1px;
3083
+ border-radius: var(--vgai-radius-sm);
3084
+ }
3085
+
3086
+ .vgai-model-tree-item {
3087
+ text-align: left;
3088
+ }
3089
+ .vgai-btn.vgai-project-tool-card {
3090
+ display: grid;
3091
+ width: 100%;
3092
+ height: auto;
3093
+ align-items: start;
3094
+ justify-content: normal;
3095
+ gap: var(--vgai-space-2);
3096
+ padding-block: var(--vgai-space-3);
3097
+ text-align: left;
3098
+ }
3099
+
3100
+ .vgai-resize-handle {
3101
+ position: relative;
3102
+ z-index: var(--vgai-z-overlay-low);
3103
+ flex: 0 0 5px;
3104
+ background: transparent;
3105
+ touch-action: none;
3106
+ }
3107
+ .vgai-resize-handle[data-orientation="vertical"] {
3108
+ cursor: col-resize;
3109
+ }
3110
+ .vgai-resize-handle[data-orientation="horizontal"] {
3111
+ cursor: row-resize;
3112
+ }
3113
+ .vgai-resize-handle::before {
3114
+ position: absolute;
3115
+ background: var(--vgai-boundary-default);
3116
+ content: "";
3117
+ transition:
3118
+ background-color 80ms var(--vgai-ease-standard),
3119
+ transform 80ms var(--vgai-ease-standard);
3120
+ }
3121
+ .vgai-resize-handle[data-orientation="vertical"]::before {
3122
+ inset: 0 auto 0 2px;
3123
+ width: 1px;
3124
+ }
3125
+ .vgai-resize-handle[data-orientation="horizontal"]::before {
3126
+ inset: 2px 0 auto;
3127
+ height: 1px;
3128
+ }
3129
+ .vgai-resize-handle:hover::before,
3130
+ .vgai-resize-handle:focus-visible::before {
3131
+ background: var(--vgai-selection-indicator);
3132
+ }
3133
+ .vgai-resize-handle[data-orientation="vertical"]:hover::before,
3134
+ .vgai-resize-handle[data-orientation="vertical"]:focus-visible::before {
3135
+ transform: scaleX(2);
3136
+ }
3137
+ .vgai-resize-handle[data-orientation="horizontal"]:hover::before,
3138
+ .vgai-resize-handle[data-orientation="horizontal"]:focus-visible::before {
3139
+ transform: scaleY(2);
3140
+ }
3141
+
3142
+ /* ---------------------------------------------------------------------- */
3143
+ /* Scrollbar policy (closes C-12, root cause of L-8/L-19) — REPLACES the */
3144
+ /* old global *::-webkit-scrollbar{display:none} suppression. Thin, dark, */
3145
+ /* themed; visible only where content actually overflows. */
3146
+ /* ---------------------------------------------------------------------- */
3147
+
3148
+ .vgai-editor-theme,
3149
+ .vgai-editor-theme :where(*):not([data-vgai-world-surface], [data-vgai-world-surface] *) {
3150
+ scrollbar-width: thin;
3151
+ scrollbar-color: var(--vgai-bg-3) transparent;
3152
+ }
3153
+ .vgai-editor-theme::-webkit-scrollbar,
3154
+ .vgai-editor-theme
3155
+ :where(*):not([data-vgai-world-surface], [data-vgai-world-surface] *)::-webkit-scrollbar {
3156
+ width: 9px;
3157
+ height: 9px;
3158
+ }
3159
+ .vgai-editor-theme::-webkit-scrollbar-track,
3160
+ .vgai-editor-theme
3161
+ :where(*):not([data-vgai-world-surface], [data-vgai-world-surface] *)::-webkit-scrollbar-track {
3162
+ background: transparent;
3163
+ }
3164
+ .vgai-editor-theme::-webkit-scrollbar-thumb,
3165
+ .vgai-editor-theme
3166
+ :where(*):not([data-vgai-world-surface], [data-vgai-world-surface] *)::-webkit-scrollbar-thumb {
3167
+ background-color: var(--vgai-bg-3);
3168
+ border-radius: var(--vgai-radius-full);
3169
+ border: 2px solid var(--vgai-bg-1);
3170
+ background-clip: padding-box;
3171
+ }
3172
+ .vgai-editor-theme::-webkit-scrollbar-thumb:hover,
3173
+ .vgai-editor-theme
3174
+ :where(*):not(
3175
+ [data-vgai-world-surface],
3176
+ [data-vgai-world-surface] *
3177
+ )::-webkit-scrollbar-thumb:hover {
3178
+ background-color: var(--vgai-border-2);
3179
+ }
3180
+ .vgai-editor-theme::-webkit-scrollbar-corner,
3181
+ .vgai-editor-theme
3182
+ :where(*):not([data-vgai-world-surface], [data-vgai-world-surface] *)::-webkit-scrollbar-corner {
3183
+ background: transparent;
3184
+ }
3185
+
3186
+ /* ---------------------------------------------------------------------- */
3187
+ /* Keyframes — keep editor-spin as-is; add the missing blink keyframe */
3188
+ /* (closes C-9, referenced by BuildPanel.tsx:325's build-log cursor). */
3189
+ /* ---------------------------------------------------------------------- */
3190
+
3191
+ @keyframes vgai-editor-spin {
3192
+ to {
3193
+ transform: rotate(360deg);
3194
+ }
3195
+ }
3196
+ @keyframes vgai-editor-blink {
3197
+ 0%,
3198
+ 100% {
3199
+ opacity: 1;
3200
+ }
3201
+ 50% {
3202
+ opacity: 0;
3203
+ }
3204
+ }
3205
+
3206
+ /* Shared animation roles — components apply these classes instead of inline
3207
+ `animation:` styles so the reduced-motion overrides below can reach them
3208
+ (a media-query rule cannot beat an inline style without !important). */
3209
+ .vgai-anim-spin {
3210
+ animation: vgai-editor-spin 0.8s linear infinite;
3211
+ }
3212
+ .vgai-anim-blink {
3213
+ animation: vgai-editor-blink 1s step-end infinite;
3214
+ }
3215
+
3216
+ /* Chrome is not prose — the VSCode model (glass-UI W7, report §2.23,
3217
+ inverted after the blanket panel-wide rule made inspector paths, console
3218
+ messages, and error text unselectable). Text is selectable by DEFAULT
3219
+ everywhere; only interaction chrome opts out below. The convicted
3220
+ defect's real vector stays closed: a drag that starts on chrome rather
3221
+ than on prose cannot anchor a selection, because chrome opts out below. A
3222
+ drag over plain prose selecting that prose
3223
+ is normal browser behavior, not a defect. `data-vgai-noselect`
3224
+ (the workspace host, static/utility panel wrappers; never documents, never
3225
+ the agent conversation) now only scopes the bare-element button rule so
3226
+ it cannot reach hosted-game DOM. */
3227
+
3228
+ /* Buttons and button-shaped triggers (labels are affordances, not text). */
3229
+ .vgai-btn,
3230
+ .vgai-list-button,
3231
+ .vgai-split-button,
3232
+ .vgai-menu,
3233
+ .vgai-menu-trigger,
3234
+ .vgai-field-trigger,
3235
+ .vgai-inspector-group-trigger,
3236
+ .vgai-status-action,
3237
+ /* Panel toolbars / headers / internal tab strips. */
3238
+ .vgai-toolbar,
3239
+ .vgai-dock-document-toolbar,
3240
+ .vgai-viewport-toolbar,
3241
+ .vgai-floating-toolbar,
3242
+ .vgai-playbar,
3243
+ .vgai-project-assets-toolbar,
3244
+ .vgai-section-header,
3245
+ .vgai-tabs,
3246
+ /* Drag-source rows/tiles: a reparent or place gesture must not paint
3247
+ selection (hierarchy rows are HTML5 drag sources; asset tiles are
3248
+ draggable when placeable; folder-tree rows are drop/drag chrome). */
3249
+ .vgai-tree-row,
3250
+ .vgai-asset-tree-row,
3251
+ .vgai-project-asset-entry,
3252
+ .vgai-model-tree-item,
3253
+ /* Scrub chrome: numeric scrubbing is a pointer drag on the readout and
3254
+ its axis label (base rules/inline styles already set these; kept here
3255
+ so the chrome inventory is complete in one place). */
3256
+ .vgai-number-input-label,
3257
+ .vgai-number-input-scrub,
3258
+ .vgai-scrub-label,
3259
+ /* Resize handles. */
3260
+ .vgai-resize-handle {
3261
+ user-select: none;
3262
+ }
3263
+ /* Bare <button>/[role=button] chrome inside editor panels only — scoped
3264
+ to the noselect markers so hosted-game DOM keeps its own defaults. */
3265
+ [data-vgai-noselect] button,
3266
+ [data-vgai-noselect] [role="button"] {
3267
+ user-select: none;
3268
+ }
3269
+ /* Form fields nested under chrome-none ancestors (e.g. the hierarchy
3270
+ rename input inside a tree row, toolbar search/filter inputs) stay
3271
+ selectable; `vgai-selectable-text` remains an explicit opt-in. */
3272
+ [data-vgai-noselect] input,
3273
+ [data-vgai-noselect] textarea,
3274
+ [data-vgai-noselect] [contenteditable="true"],
3275
+ .vgai-toolbar input,
3276
+ .vgai-dock-document-toolbar input,
3277
+ .vgai-project-assets-toolbar input,
3278
+ .vgai-tree-row input,
3279
+ .vgai-selectable-text {
3280
+ user-select: text;
3281
+ }
3282
+
3283
+ /* Light Explorer — a wide, scene-level audit table in the on-demand bottom
3284
+ utility group. Interaction paint stays on the shared controls; this block
3285
+ owns only the table's geometry and semantic selection/readability states. */
3286
+ .vgai-light-explorer {
3287
+ display: flex;
3288
+ flex-direction: column;
3289
+ min-width: 0;
3290
+ height: 100%;
3291
+ color: var(--vgai-content-primary);
3292
+ background: var(--vgai-surface-panel);
3293
+ }
3294
+
3295
+ .vgai-light-explorer-toolbar {
3296
+ display: flex;
3297
+ align-items: center;
3298
+ gap: var(--vgai-space-2);
3299
+ min-height: 38px;
3300
+ padding: var(--vgai-space-2) var(--vgai-space-3);
3301
+ border-bottom: var(--vgai-stroke-resting) solid var(--vgai-structural-divider);
3302
+ }
3303
+
3304
+ .vgai-light-explorer-toolbar > .vgai-input {
3305
+ width: min(280px, 38vw);
3306
+ }
3307
+
3308
+ .vgai-light-explorer-summary {
3309
+ margin-left: auto;
3310
+ color: var(--vgai-content-muted);
3311
+ font-size: var(--vgai-font-small);
3312
+ white-space: nowrap;
3313
+ }
3314
+
3315
+ .vgai-light-explorer-scroll {
3316
+ min-height: 0;
3317
+ flex: 1;
3318
+ overflow: auto;
3319
+ }
3320
+
3321
+ .vgai-light-explorer-table {
3322
+ width: 100%;
3323
+ min-width: 760px;
3324
+ border-collapse: separate;
3325
+ border-spacing: 0;
3326
+ font-size: var(--vgai-font-base);
3327
+ }
3328
+
3329
+ .vgai-light-explorer-table th {
3330
+ position: sticky;
3331
+ top: 0;
3332
+ z-index: 1;
3333
+ height: 30px;
3334
+ padding: 0 var(--vgai-space-2);
3335
+ text-align: left;
3336
+ font-weight: var(--vgai-font-weight-medium);
3337
+ color: var(--vgai-content-muted);
3338
+ background: var(--vgai-surface-raised);
3339
+ border-bottom: var(--vgai-stroke-resting) solid var(--vgai-structural-divider);
3340
+ }
3341
+
3342
+ .vgai-light-explorer-table td {
3343
+ height: 34px;
3344
+ padding: var(--vgai-space-1) var(--vgai-space-2);
3345
+ border-bottom: var(--vgai-stroke-resting) solid var(--vgai-structural-divider);
3346
+ vertical-align: middle;
3347
+ }
3348
+
3349
+ .vgai-light-explorer-table tbody tr[data-selected="true"] td {
3350
+ background: var(--vgai-selection-bg);
3351
+ }
3352
+
3353
+ .vgai-light-explorer-table tbody tr:hover td {
3354
+ background: var(--vgai-neutral-hover);
3355
+ }
3356
+
3357
+ .vgai-light-explorer-table tbody tr[data-selected="true"]:hover td {
3358
+ background: var(--vgai-selection-bg);
3359
+ }
3360
+
3361
+ .vgai-light-explorer-check-column,
3362
+ .vgai-light-explorer-table td:first-child,
3363
+ .vgai-light-explorer-table td:last-child {
3364
+ width: 54px;
3365
+ text-align: center;
3366
+ }
3367
+
3368
+ .vgai-light-explorer-sort.vgai-btn,
3369
+ .vgai-light-explorer-name.vgai-btn {
3370
+ justify-content: flex-start;
3371
+ min-width: 0;
3372
+ padding-inline: 0;
3373
+ }
3374
+
3375
+ .vgai-light-explorer-name.vgai-btn {
3376
+ width: 100%;
3377
+ color: var(--vgai-content-primary);
3378
+ }
3379
+
3380
+ .vgai-light-explorer-type,
3381
+ .vgai-light-explorer-color span {
3382
+ color: var(--vgai-content-muted);
3383
+ font-family: var(--vgai-font-mono);
3384
+ font-size: var(--vgai-font-small);
3385
+ }
3386
+
3387
+ .vgai-light-explorer-color {
3388
+ display: flex;
3389
+ align-items: center;
3390
+ gap: var(--vgai-space-2);
3391
+ white-space: nowrap;
3392
+ }
3393
+
3394
+ .vgai-light-explorer-table .vgai-number-input {
3395
+ width: 86px;
3396
+ }
3397
+
3398
+ .vgai-light-explorer-unavailable {
3399
+ color: var(--vgai-content-dim);
3400
+ }
3401
+
3402
+ .vgai-light-explorer-empty {
3403
+ padding: var(--vgai-space-4);
3404
+ color: var(--vgai-content-muted);
3405
+ font-style: italic;
3406
+ }
3407
+
3408
+ /* ---------------------------------------------------------------------- */
3409
+ /* Reduced motion. Color/opacity fades elsewhere are not vestibular */
3410
+ /* triggers and stay; this gates the genuine motion: continuous rotation, */
3411
+ /* the flashing build-log cursor, and the resize-handle scale transition */
3412
+ /* (the widened hover state itself stays — instant, not animated). */
3413
+ /* ---------------------------------------------------------------------- */
3414
+
3415
+ @media (prefers-reduced-motion: reduce) {
3416
+ .vgai-anim-spin,
3417
+ .vgai-state-surface[data-tone="loading"] .vgai-state-surface-icon {
3418
+ /* Stepped rotation still reads as activity without continuous motion. */
3419
+ animation-duration: 1.6s;
3420
+ animation-timing-function: steps(8, end);
3421
+ }
3422
+ .vgai-anim-blink {
3423
+ animation: none;
3424
+ opacity: 1;
3425
+ }
3426
+ .vgai-resize-handle::before {
3427
+ transition-property: background-color;
3428
+ }
3429
+ }
3430
+
3431
+ /* Project-work conflict/stale notice — a ghost text-button in warning ink
3432
+ (stylesheet re-scope; inline Button color is guard-banned). */
3433
+ .vgai-project-work-conflict {
3434
+ color: var(--vgai-warn);
3435
+ font-size: var(--vgai-font-sm);
3436
+ text-align: left;
3437
+ }