This is page 57 of 60. Use http://codebase.md/hangwin/mcp-chrome?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .gitattributes
├── .github
│ └── workflows
│ └── build-release.yml
├── .gitignore
├── .husky
│ ├── commit-msg
│ └── pre-commit
├── .prettierignore
├── .prettierrc.json
├── .vscode
│ └── extensions.json
├── app
│ ├── chrome-extension
│ │ ├── _locales
│ │ │ ├── de
│ │ │ │ └── messages.json
│ │ │ ├── en
│ │ │ │ └── messages.json
│ │ │ ├── ja
│ │ │ │ └── messages.json
│ │ │ ├── ko
│ │ │ │ └── messages.json
│ │ │ ├── zh_CN
│ │ │ │ └── messages.json
│ │ │ └── zh_TW
│ │ │ └── messages.json
│ │ ├── .env.example
│ │ ├── assets
│ │ │ └── vue.svg
│ │ ├── common
│ │ │ ├── agent-models.ts
│ │ │ ├── constants.ts
│ │ │ ├── element-marker-types.ts
│ │ │ ├── message-types.ts
│ │ │ ├── node-types.ts
│ │ │ ├── rr-v3-keepalive-protocol.ts
│ │ │ ├── step-types.ts
│ │ │ ├── tool-handler.ts
│ │ │ └── web-editor-types.ts
│ │ ├── entrypoints
│ │ │ ├── background
│ │ │ │ ├── element-marker
│ │ │ │ │ ├── element-marker-storage.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── keepalive-manager.ts
│ │ │ │ ├── native-host.ts
│ │ │ │ ├── quick-panel
│ │ │ │ │ ├── agent-handler.ts
│ │ │ │ │ ├── commands.ts
│ │ │ │ │ └── tabs-handler.ts
│ │ │ │ ├── record-replay
│ │ │ │ │ ├── actions
│ │ │ │ │ │ ├── adapter.ts
│ │ │ │ │ │ ├── handlers
│ │ │ │ │ │ │ ├── assert.ts
│ │ │ │ │ │ │ ├── click.ts
│ │ │ │ │ │ │ ├── common.ts
│ │ │ │ │ │ │ ├── control-flow.ts
│ │ │ │ │ │ │ ├── delay.ts
│ │ │ │ │ │ │ ├── dom.ts
│ │ │ │ │ │ │ ├── drag.ts
│ │ │ │ │ │ │ ├── extract.ts
│ │ │ │ │ │ │ ├── fill.ts
│ │ │ │ │ │ │ ├── http.ts
│ │ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ │ ├── key.ts
│ │ │ │ │ │ │ ├── navigate.ts
│ │ │ │ │ │ │ ├── screenshot.ts
│ │ │ │ │ │ │ ├── script.ts
│ │ │ │ │ │ │ ├── scroll.ts
│ │ │ │ │ │ │ ├── tabs.ts
│ │ │ │ │ │ │ └── wait.ts
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ ├── registry.ts
│ │ │ │ │ │ └── types.ts
│ │ │ │ │ ├── engine
│ │ │ │ │ │ ├── constants.ts
│ │ │ │ │ │ ├── execution-mode.ts
│ │ │ │ │ │ ├── logging
│ │ │ │ │ │ │ └── run-logger.ts
│ │ │ │ │ │ ├── plugins
│ │ │ │ │ │ │ ├── breakpoint.ts
│ │ │ │ │ │ │ ├── manager.ts
│ │ │ │ │ │ │ └── types.ts
│ │ │ │ │ │ ├── policies
│ │ │ │ │ │ │ ├── retry.ts
│ │ │ │ │ │ │ └── wait.ts
│ │ │ │ │ │ ├── runners
│ │ │ │ │ │ │ ├── after-script-queue.ts
│ │ │ │ │ │ │ ├── control-flow-runner.ts
│ │ │ │ │ │ │ ├── step-executor.ts
│ │ │ │ │ │ │ ├── step-runner.ts
│ │ │ │ │ │ │ └── subflow-runner.ts
│ │ │ │ │ │ ├── scheduler.ts
│ │ │ │ │ │ ├── state-manager.ts
│ │ │ │ │ │ └── utils
│ │ │ │ │ │ └── expression.ts
│ │ │ │ │ ├── flow-runner.ts
│ │ │ │ │ ├── flow-store.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── legacy-types.ts
│ │ │ │ │ ├── nodes
│ │ │ │ │ │ ├── assert.ts
│ │ │ │ │ │ ├── click.ts
│ │ │ │ │ │ ├── conditional.ts
│ │ │ │ │ │ ├── download-screenshot-attr-event-frame-loop.ts
│ │ │ │ │ │ ├── drag.ts
│ │ │ │ │ │ ├── execute-flow.ts
│ │ │ │ │ │ ├── extract.ts
│ │ │ │ │ │ ├── fill.ts
│ │ │ │ │ │ ├── http.ts
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ ├── key.ts
│ │ │ │ │ │ ├── loops.ts
│ │ │ │ │ │ ├── navigate.ts
│ │ │ │ │ │ ├── script.ts
│ │ │ │ │ │ ├── scroll.ts
│ │ │ │ │ │ ├── tabs.ts
│ │ │ │ │ │ ├── types.ts
│ │ │ │ │ │ └── wait.ts
│ │ │ │ │ ├── recording
│ │ │ │ │ │ ├── browser-event-listener.ts
│ │ │ │ │ │ ├── content-injection.ts
│ │ │ │ │ │ ├── content-message-handler.ts
│ │ │ │ │ │ ├── flow-builder.ts
│ │ │ │ │ │ ├── recorder-manager.ts
│ │ │ │ │ │ └── session-manager.ts
│ │ │ │ │ ├── rr-utils.ts
│ │ │ │ │ ├── selector-engine.ts
│ │ │ │ │ ├── storage
│ │ │ │ │ │ └── indexeddb-manager.ts
│ │ │ │ │ ├── trigger-store.ts
│ │ │ │ │ └── types.ts
│ │ │ │ ├── record-replay-v3
│ │ │ │ │ ├── bootstrap.ts
│ │ │ │ │ ├── domain
│ │ │ │ │ │ ├── debug.ts
│ │ │ │ │ │ ├── errors.ts
│ │ │ │ │ │ ├── events.ts
│ │ │ │ │ │ ├── flow.ts
│ │ │ │ │ │ ├── ids.ts
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ ├── json.ts
│ │ │ │ │ │ ├── policy.ts
│ │ │ │ │ │ ├── triggers.ts
│ │ │ │ │ │ └── variables.ts
│ │ │ │ │ ├── engine
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ ├── keepalive
│ │ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ │ └── offscreen-keepalive.ts
│ │ │ │ │ │ ├── kernel
│ │ │ │ │ │ │ ├── artifacts.ts
│ │ │ │ │ │ │ ├── breakpoints.ts
│ │ │ │ │ │ │ ├── debug-controller.ts
│ │ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ │ ├── kernel.ts
│ │ │ │ │ │ │ ├── recovery-kernel.ts
│ │ │ │ │ │ │ ├── runner.ts
│ │ │ │ │ │ │ └── traversal.ts
│ │ │ │ │ │ ├── plugins
│ │ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ │ ├── register-v2-replay-nodes.ts
│ │ │ │ │ │ │ ├── registry.ts
│ │ │ │ │ │ │ ├── types.ts
│ │ │ │ │ │ │ └── v2-action-adapter.ts
│ │ │ │ │ │ ├── queue
│ │ │ │ │ │ │ ├── enqueue-run.ts
│ │ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ │ ├── leasing.ts
│ │ │ │ │ │ │ ├── queue.ts
│ │ │ │ │ │ │ └── scheduler.ts
│ │ │ │ │ │ ├── recovery
│ │ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ │ └── recovery-coordinator.ts
│ │ │ │ │ │ ├── storage
│ │ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ │ └── storage-port.ts
│ │ │ │ │ │ ├── transport
│ │ │ │ │ │ │ ├── events-bus.ts
│ │ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ │ ├── rpc-server.ts
│ │ │ │ │ │ │ └── rpc.ts
│ │ │ │ │ │ └── triggers
│ │ │ │ │ │ ├── command-trigger.ts
│ │ │ │ │ │ ├── context-menu-trigger.ts
│ │ │ │ │ │ ├── cron-trigger.ts
│ │ │ │ │ │ ├── dom-trigger.ts
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ ├── interval-trigger.ts
│ │ │ │ │ │ ├── manual-trigger.ts
│ │ │ │ │ │ ├── once-trigger.ts
│ │ │ │ │ │ ├── trigger-handler.ts
│ │ │ │ │ │ ├── trigger-manager.ts
│ │ │ │ │ │ └── url-trigger.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── storage
│ │ │ │ │ ├── db.ts
│ │ │ │ │ ├── events.ts
│ │ │ │ │ ├── flows.ts
│ │ │ │ │ ├── import
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ ├── v2-reader.ts
│ │ │ │ │ │ └── v2-to-v3.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── persistent-vars.ts
│ │ │ │ │ ├── queue.ts
│ │ │ │ │ ├── runs.ts
│ │ │ │ │ └── triggers.ts
│ │ │ │ ├── semantic-similarity.ts
│ │ │ │ ├── storage-manager.ts
│ │ │ │ ├── tools
│ │ │ │ │ ├── base-browser.ts
│ │ │ │ │ ├── browser
│ │ │ │ │ │ ├── bookmark.ts
│ │ │ │ │ │ ├── common.ts
│ │ │ │ │ │ ├── computer.ts
│ │ │ │ │ │ ├── console-buffer.ts
│ │ │ │ │ │ ├── console.ts
│ │ │ │ │ │ ├── dialog.ts
│ │ │ │ │ │ ├── download.ts
│ │ │ │ │ │ ├── element-picker.ts
│ │ │ │ │ │ ├── file-upload.ts
│ │ │ │ │ │ ├── gif-auto-capture.ts
│ │ │ │ │ │ ├── gif-enhanced-renderer.ts
│ │ │ │ │ │ ├── gif-recorder.ts
│ │ │ │ │ │ ├── history.ts
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ ├── inject-script.ts
│ │ │ │ │ │ ├── interaction.ts
│ │ │ │ │ │ ├── javascript.ts
│ │ │ │ │ │ ├── keyboard.ts
│ │ │ │ │ │ ├── network-capture-debugger.ts
│ │ │ │ │ │ ├── network-capture-web-request.ts
│ │ │ │ │ │ ├── network-capture.ts
│ │ │ │ │ │ ├── network-request.ts
│ │ │ │ │ │ ├── performance.ts
│ │ │ │ │ │ ├── read-page.ts
│ │ │ │ │ │ ├── screenshot.ts
│ │ │ │ │ │ ├── userscript.ts
│ │ │ │ │ │ ├── vector-search.ts
│ │ │ │ │ │ ├── web-fetcher.ts
│ │ │ │ │ │ └── window.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── record-replay.ts
│ │ │ │ ├── utils
│ │ │ │ │ └── sidepanel.ts
│ │ │ │ └── web-editor
│ │ │ │ └── index.ts
│ │ │ ├── builder
│ │ │ │ ├── App.vue
│ │ │ │ ├── index.html
│ │ │ │ └── main.ts
│ │ │ ├── content.ts
│ │ │ ├── element-picker.content.ts
│ │ │ ├── offscreen
│ │ │ │ ├── gif-encoder.ts
│ │ │ │ ├── index.html
│ │ │ │ ├── main.ts
│ │ │ │ └── rr-keepalive.ts
│ │ │ ├── options
│ │ │ │ ├── App.vue
│ │ │ │ ├── index.html
│ │ │ │ └── main.ts
│ │ │ ├── popup
│ │ │ │ ├── App.vue
│ │ │ │ ├── components
│ │ │ │ │ ├── builder
│ │ │ │ │ │ ├── components
│ │ │ │ │ │ │ ├── Canvas.vue
│ │ │ │ │ │ │ ├── EdgePropertyPanel.vue
│ │ │ │ │ │ │ ├── KeyValueEditor.vue
│ │ │ │ │ │ │ ├── nodes
│ │ │ │ │ │ │ │ ├── node-util.ts
│ │ │ │ │ │ │ │ ├── NodeCard.vue
│ │ │ │ │ │ │ │ └── NodeIf.vue
│ │ │ │ │ │ │ ├── properties
│ │ │ │ │ │ │ │ ├── PropertyAssert.vue
│ │ │ │ │ │ │ │ ├── PropertyClick.vue
│ │ │ │ │ │ │ │ ├── PropertyCloseTab.vue
│ │ │ │ │ │ │ │ ├── PropertyDelay.vue
│ │ │ │ │ │ │ │ ├── PropertyDrag.vue
│ │ │ │ │ │ │ │ ├── PropertyExecuteFlow.vue
│ │ │ │ │ │ │ │ ├── PropertyExtract.vue
│ │ │ │ │ │ │ │ ├── PropertyFill.vue
│ │ │ │ │ │ │ │ ├── PropertyForeach.vue
│ │ │ │ │ │ │ │ ├── PropertyFormRenderer.vue
│ │ │ │ │ │ │ │ ├── PropertyFromSpec.vue
│ │ │ │ │ │ │ │ ├── PropertyHandleDownload.vue
│ │ │ │ │ │ │ │ ├── PropertyHttp.vue
│ │ │ │ │ │ │ │ ├── PropertyIf.vue
│ │ │ │ │ │ │ │ ├── PropertyKey.vue
│ │ │ │ │ │ │ │ ├── PropertyLoopElements.vue
│ │ │ │ │ │ │ │ ├── PropertyNavigate.vue
│ │ │ │ │ │ │ │ ├── PropertyOpenTab.vue
│ │ │ │ │ │ │ │ ├── PropertyScreenshot.vue
│ │ │ │ │ │ │ │ ├── PropertyScript.vue
│ │ │ │ │ │ │ │ ├── PropertyScroll.vue
│ │ │ │ │ │ │ │ ├── PropertySetAttribute.vue
│ │ │ │ │ │ │ │ ├── PropertySwitchFrame.vue
│ │ │ │ │ │ │ │ ├── PropertySwitchTab.vue
│ │ │ │ │ │ │ │ ├── PropertyTrigger.vue
│ │ │ │ │ │ │ │ ├── PropertyTriggerEvent.vue
│ │ │ │ │ │ │ │ ├── PropertyWait.vue
│ │ │ │ │ │ │ │ ├── PropertyWhile.vue
│ │ │ │ │ │ │ │ └── SelectorEditor.vue
│ │ │ │ │ │ │ ├── PropertyPanel.vue
│ │ │ │ │ │ │ ├── Sidebar.vue
│ │ │ │ │ │ │ └── TriggerPanel.vue
│ │ │ │ │ │ ├── model
│ │ │ │ │ │ │ ├── form-widget-registry.ts
│ │ │ │ │ │ │ ├── node-spec-registry.ts
│ │ │ │ │ │ │ ├── node-spec.ts
│ │ │ │ │ │ │ ├── node-specs-builtin.ts
│ │ │ │ │ │ │ ├── toast.ts
│ │ │ │ │ │ │ ├── transforms.ts
│ │ │ │ │ │ │ ├── ui-nodes.ts
│ │ │ │ │ │ │ ├── validation.ts
│ │ │ │ │ │ │ └── variables.ts
│ │ │ │ │ │ ├── store
│ │ │ │ │ │ │ └── useBuilderStore.ts
│ │ │ │ │ │ └── widgets
│ │ │ │ │ │ ├── FieldCode.vue
│ │ │ │ │ │ ├── FieldDuration.vue
│ │ │ │ │ │ ├── FieldExpression.vue
│ │ │ │ │ │ ├── FieldKeySequence.vue
│ │ │ │ │ │ ├── FieldSelector.vue
│ │ │ │ │ │ ├── FieldTargetLocator.vue
│ │ │ │ │ │ └── VarInput.vue
│ │ │ │ │ ├── ConfirmDialog.vue
│ │ │ │ │ ├── ElementMarkerManagement.vue
│ │ │ │ │ ├── icons
│ │ │ │ │ │ ├── BoltIcon.vue
│ │ │ │ │ │ ├── CheckIcon.vue
│ │ │ │ │ │ ├── DatabaseIcon.vue
│ │ │ │ │ │ ├── DocumentIcon.vue
│ │ │ │ │ │ ├── EditIcon.vue
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ ├── MarkerIcon.vue
│ │ │ │ │ │ ├── RecordIcon.vue
│ │ │ │ │ │ ├── RefreshIcon.vue
│ │ │ │ │ │ ├── StopIcon.vue
│ │ │ │ │ │ ├── TabIcon.vue
│ │ │ │ │ │ ├── TrashIcon.vue
│ │ │ │ │ │ ├── VectorIcon.vue
│ │ │ │ │ │ └── WorkflowIcon.vue
│ │ │ │ │ ├── LocalModelPage.vue
│ │ │ │ │ ├── ModelCacheManagement.vue
│ │ │ │ │ ├── ProgressIndicator.vue
│ │ │ │ │ └── ScheduleDialog.vue
│ │ │ │ ├── index.html
│ │ │ │ ├── main.ts
│ │ │ │ └── style.css
│ │ │ ├── quick-panel.content.ts
│ │ │ ├── shared
│ │ │ │ ├── composables
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── useRRV3Rpc.ts
│ │ │ │ └── utils
│ │ │ │ ├── index.ts
│ │ │ │ └── rr-flow-convert.ts
│ │ │ ├── sidepanel
│ │ │ │ ├── App.vue
│ │ │ │ ├── components
│ │ │ │ │ ├── agent
│ │ │ │ │ │ ├── AttachmentPreview.vue
│ │ │ │ │ │ ├── ChatInput.vue
│ │ │ │ │ │ ├── CliSettings.vue
│ │ │ │ │ │ ├── ConnectionStatus.vue
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ ├── MessageItem.vue
│ │ │ │ │ │ ├── MessageList.vue
│ │ │ │ │ │ ├── ProjectCreateForm.vue
│ │ │ │ │ │ └── ProjectSelector.vue
│ │ │ │ │ ├── agent-chat
│ │ │ │ │ │ ├── AgentChatShell.vue
│ │ │ │ │ │ ├── AgentComposer.vue
│ │ │ │ │ │ ├── AgentConversation.vue
│ │ │ │ │ │ ├── AgentOpenProjectMenu.vue
│ │ │ │ │ │ ├── AgentProjectMenu.vue
│ │ │ │ │ │ ├── AgentRequestThread.vue
│ │ │ │ │ │ ├── AgentSessionListItem.vue
│ │ │ │ │ │ ├── AgentSessionMenu.vue
│ │ │ │ │ │ ├── AgentSessionSettingsPanel.vue
│ │ │ │ │ │ ├── AgentSessionsView.vue
│ │ │ │ │ │ ├── AgentSettingsMenu.vue
│ │ │ │ │ │ ├── AgentTimeline.vue
│ │ │ │ │ │ ├── AgentTimelineItem.vue
│ │ │ │ │ │ ├── AgentTopBar.vue
│ │ │ │ │ │ ├── ApplyMessageChip.vue
│ │ │ │ │ │ ├── AttachmentCachePanel.vue
│ │ │ │ │ │ ├── ComposerDrawer.vue
│ │ │ │ │ │ ├── ElementChip.vue
│ │ │ │ │ │ ├── FakeCaretOverlay.vue
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ ├── SelectionChip.vue
│ │ │ │ │ │ ├── timeline
│ │ │ │ │ │ │ ├── markstream-thinking.ts
│ │ │ │ │ │ │ ├── ThinkingNode.vue
│ │ │ │ │ │ │ ├── TimelineNarrativeStep.vue
│ │ │ │ │ │ │ ├── TimelineStatusStep.vue
│ │ │ │ │ │ │ ├── TimelineToolCallStep.vue
│ │ │ │ │ │ │ ├── TimelineToolResultCardStep.vue
│ │ │ │ │ │ │ └── TimelineUserPromptStep.vue
│ │ │ │ │ │ └── WebEditorChanges.vue
│ │ │ │ │ ├── AgentChat.vue
│ │ │ │ │ ├── rr-v3
│ │ │ │ │ │ └── DebuggerPanel.vue
│ │ │ │ │ ├── SidepanelNavigator.vue
│ │ │ │ │ └── workflows
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── WorkflowListItem.vue
│ │ │ │ │ └── WorkflowsView.vue
│ │ │ │ ├── composables
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── useAgentChat.ts
│ │ │ │ │ ├── useAgentChatViewRoute.ts
│ │ │ │ │ ├── useAgentInputPreferences.ts
│ │ │ │ │ ├── useAgentProjects.ts
│ │ │ │ │ ├── useAgentServer.ts
│ │ │ │ │ ├── useAgentSessions.ts
│ │ │ │ │ ├── useAgentTheme.ts
│ │ │ │ │ ├── useAgentThreads.ts
│ │ │ │ │ ├── useAttachments.ts
│ │ │ │ │ ├── useFakeCaret.ts
│ │ │ │ │ ├── useFloatingDrag.ts
│ │ │ │ │ ├── useOpenProjectPreference.ts
│ │ │ │ │ ├── useRRV3Debugger.ts
│ │ │ │ │ ├── useRRV3Rpc.ts
│ │ │ │ │ ├── useTextareaAutoResize.ts
│ │ │ │ │ ├── useWebEditorTxState.ts
│ │ │ │ │ └── useWorkflowsV3.ts
│ │ │ │ ├── index.html
│ │ │ │ ├── main.ts
│ │ │ │ ├── styles
│ │ │ │ │ └── agent-chat.css
│ │ │ │ └── utils
│ │ │ │ └── loading-texts.ts
│ │ │ ├── styles
│ │ │ │ └── tailwind.css
│ │ │ ├── web-editor-v2
│ │ │ │ ├── attr-ui-refactor.md
│ │ │ │ ├── constants.ts
│ │ │ │ ├── core
│ │ │ │ │ ├── css-compare.ts
│ │ │ │ │ ├── cssom-styles-collector.ts
│ │ │ │ │ ├── debug-source.ts
│ │ │ │ │ ├── design-tokens
│ │ │ │ │ │ ├── design-tokens-service.ts
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ ├── token-detector.ts
│ │ │ │ │ │ ├── token-resolver.ts
│ │ │ │ │ │ └── types.ts
│ │ │ │ │ ├── editor.ts
│ │ │ │ │ ├── element-key.ts
│ │ │ │ │ ├── event-controller.ts
│ │ │ │ │ ├── execution-tracker.ts
│ │ │ │ │ ├── hmr-consistency.ts
│ │ │ │ │ ├── locator.ts
│ │ │ │ │ ├── message-listener.ts
│ │ │ │ │ ├── payload-builder.ts
│ │ │ │ │ ├── perf-monitor.ts
│ │ │ │ │ ├── position-tracker.ts
│ │ │ │ │ ├── props-bridge.ts
│ │ │ │ │ ├── snap-engine.ts
│ │ │ │ │ ├── transaction-aggregator.ts
│ │ │ │ │ └── transaction-manager.ts
│ │ │ │ ├── drag
│ │ │ │ │ └── drag-reorder-controller.ts
│ │ │ │ ├── overlay
│ │ │ │ │ ├── canvas-overlay.ts
│ │ │ │ │ └── handles-controller.ts
│ │ │ │ ├── selection
│ │ │ │ │ └── selection-engine.ts
│ │ │ │ ├── ui
│ │ │ │ │ ├── breadcrumbs.ts
│ │ │ │ │ ├── floating-drag.ts
│ │ │ │ │ ├── icons.ts
│ │ │ │ │ ├── property-panel
│ │ │ │ │ │ ├── class-editor.ts
│ │ │ │ │ │ ├── components
│ │ │ │ │ │ │ ├── alignment-grid.ts
│ │ │ │ │ │ │ ├── icon-button-group.ts
│ │ │ │ │ │ │ ├── input-container.ts
│ │ │ │ │ │ │ ├── slider-input.ts
│ │ │ │ │ │ │ └── token-pill.ts
│ │ │ │ │ │ ├── components-tree.ts
│ │ │ │ │ │ ├── controls
│ │ │ │ │ │ │ ├── appearance-control.ts
│ │ │ │ │ │ │ ├── background-control.ts
│ │ │ │ │ │ │ ├── border-control.ts
│ │ │ │ │ │ │ ├── color-field.ts
│ │ │ │ │ │ │ ├── css-helpers.ts
│ │ │ │ │ │ │ ├── effects-control.ts
│ │ │ │ │ │ │ ├── gradient-control.ts
│ │ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ │ ├── layout-control.ts
│ │ │ │ │ │ │ ├── number-stepping.ts
│ │ │ │ │ │ │ ├── position-control.ts
│ │ │ │ │ │ │ ├── size-control.ts
│ │ │ │ │ │ │ ├── spacing-control.ts
│ │ │ │ │ │ │ ├── token-picker.ts
│ │ │ │ │ │ │ └── typography-control.ts
│ │ │ │ │ │ ├── css-defaults.ts
│ │ │ │ │ │ ├── css-panel.ts
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ ├── property-panel.ts
│ │ │ │ │ │ ├── props-panel.ts
│ │ │ │ │ │ └── types.ts
│ │ │ │ │ ├── shadow-host.ts
│ │ │ │ │ └── toolbar.ts
│ │ │ │ └── utils
│ │ │ │ └── disposables.ts
│ │ │ ├── web-editor-v2.ts
│ │ │ └── welcome
│ │ │ ├── App.vue
│ │ │ ├── index.html
│ │ │ └── main.ts
│ │ ├── env.d.ts
│ │ ├── eslint.config.js
│ │ ├── inject-scripts
│ │ │ ├── accessibility-tree-helper.js
│ │ │ ├── click-helper.js
│ │ │ ├── dom-observer.js
│ │ │ ├── element-marker.js
│ │ │ ├── element-picker.js
│ │ │ ├── fill-helper.js
│ │ │ ├── inject-bridge.js
│ │ │ ├── interactive-elements-helper.js
│ │ │ ├── keyboard-helper.js
│ │ │ ├── network-helper.js
│ │ │ ├── props-agent.js
│ │ │ ├── recorder.js
│ │ │ ├── screenshot-helper.js
│ │ │ ├── wait-helper.js
│ │ │ ├── web-editor.js
│ │ │ └── web-fetcher-helper.js
│ │ ├── LICENSE
│ │ ├── package.json
│ │ ├── public
│ │ │ ├── icon
│ │ │ │ ├── 128.png
│ │ │ │ ├── 16.png
│ │ │ │ ├── 32.png
│ │ │ │ ├── 48.png
│ │ │ │ └── 96.png
│ │ │ ├── libs
│ │ │ │ └── ort.min.js
│ │ │ └── wxt.svg
│ │ ├── README.md
│ │ ├── shared
│ │ │ ├── element-picker
│ │ │ │ ├── controller.ts
│ │ │ │ └── index.ts
│ │ │ ├── quick-panel
│ │ │ │ ├── core
│ │ │ │ │ ├── agent-bridge.ts
│ │ │ │ │ ├── search-engine.ts
│ │ │ │ │ └── types.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── providers
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── tabs-provider.ts
│ │ │ │ └── ui
│ │ │ │ ├── ai-chat-panel.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── markdown-renderer.ts
│ │ │ │ ├── message-renderer.ts
│ │ │ │ ├── panel-shell.ts
│ │ │ │ ├── quick-entries.ts
│ │ │ │ ├── search-input.ts
│ │ │ │ ├── shadow-host.ts
│ │ │ │ └── styles.ts
│ │ │ └── selector
│ │ │ ├── dom-path.ts
│ │ │ ├── fingerprint.ts
│ │ │ ├── generator.ts
│ │ │ ├── index.ts
│ │ │ ├── locator.ts
│ │ │ ├── shadow-dom.ts
│ │ │ ├── stability.ts
│ │ │ ├── strategies
│ │ │ │ ├── anchor-relpath.ts
│ │ │ │ ├── aria.ts
│ │ │ │ ├── css-path.ts
│ │ │ │ ├── css-unique.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── testid.ts
│ │ │ │ └── text.ts
│ │ │ └── types.ts
│ │ ├── tailwind.config.ts
│ │ ├── tests
│ │ │ ├── __mocks__
│ │ │ │ └── hnswlib-wasm-static.ts
│ │ │ ├── record-replay
│ │ │ │ ├── _test-helpers.ts
│ │ │ │ ├── adapter-policy.contract.test.ts
│ │ │ │ ├── flow-store-strip-steps.contract.test.ts
│ │ │ │ ├── high-risk-actions.integration.test.ts
│ │ │ │ ├── hybrid-actions.integration.test.ts
│ │ │ │ ├── script-control-flow.integration.test.ts
│ │ │ │ ├── session-dag-sync.contract.test.ts
│ │ │ │ ├── step-executor.contract.test.ts
│ │ │ │ └── tab-cursor.integration.test.ts
│ │ │ ├── record-replay-v3
│ │ │ │ ├── command-trigger.test.ts
│ │ │ │ ├── context-menu-trigger.test.ts
│ │ │ │ ├── cron-trigger.test.ts
│ │ │ │ ├── debugger.contract.test.ts
│ │ │ │ ├── dom-trigger.test.ts
│ │ │ │ ├── e2e.integration.test.ts
│ │ │ │ ├── events.contract.test.ts
│ │ │ │ ├── interval-trigger.test.ts
│ │ │ │ ├── manual-trigger.test.ts
│ │ │ │ ├── once-trigger.test.ts
│ │ │ │ ├── queue.contract.test.ts
│ │ │ │ ├── recovery.test.ts
│ │ │ │ ├── rpc-api.test.ts
│ │ │ │ ├── runner.onError.contract.test.ts
│ │ │ │ ├── scheduler-integration.test.ts
│ │ │ │ ├── scheduler.test.ts
│ │ │ │ ├── spec-smoke.test.ts
│ │ │ │ ├── trigger-manager.test.ts
│ │ │ │ ├── triggers.test.ts
│ │ │ │ ├── url-trigger.test.ts
│ │ │ │ ├── v2-action-adapter.test.ts
│ │ │ │ ├── v2-adapter-integration.test.ts
│ │ │ │ ├── v2-to-v3-conversion.test.ts
│ │ │ │ └── v3-e2e-harness.ts
│ │ │ ├── vitest.setup.ts
│ │ │ └── web-editor-v2
│ │ │ ├── design-tokens.test.ts
│ │ │ ├── drag-reorder-controller.test.ts
│ │ │ ├── event-controller.test.ts
│ │ │ ├── locator.test.ts
│ │ │ ├── property-panel-live-sync.test.ts
│ │ │ ├── selection-engine.test.ts
│ │ │ ├── snap-engine.test.ts
│ │ │ └── test-utils
│ │ │ └── dom.ts
│ │ ├── tsconfig.json
│ │ ├── types
│ │ │ ├── gifenc.d.ts
│ │ │ └── icons.d.ts
│ │ ├── utils
│ │ │ ├── cdp-session-manager.ts
│ │ │ ├── content-indexer.ts
│ │ │ ├── i18n.ts
│ │ │ ├── image-utils.ts
│ │ │ ├── indexeddb-client.ts
│ │ │ ├── lru-cache.ts
│ │ │ ├── model-cache-manager.ts
│ │ │ ├── offscreen-manager.ts
│ │ │ ├── output-sanitizer.ts
│ │ │ ├── screenshot-context.ts
│ │ │ ├── semantic-similarity-engine.ts
│ │ │ ├── simd-math-engine.ts
│ │ │ ├── text-chunker.ts
│ │ │ └── vector-database.ts
│ │ ├── vitest.config.ts
│ │ ├── workers
│ │ │ ├── ort-wasm-simd-threaded.jsep.mjs
│ │ │ ├── ort-wasm-simd-threaded.jsep.wasm
│ │ │ ├── ort-wasm-simd-threaded.mjs
│ │ │ ├── ort-wasm-simd-threaded.wasm
│ │ │ ├── simd_math_bg.wasm
│ │ │ ├── simd_math.js
│ │ │ └── similarity.worker.js
│ │ └── wxt.config.ts
│ └── native-server
│ ├── .npmignore
│ ├── debug.sh
│ ├── install.md
│ ├── jest.config.js
│ ├── package.json
│ ├── README.md
│ ├── src
│ │ ├── agent
│ │ │ ├── attachment-service.ts
│ │ │ ├── ccr-detector.ts
│ │ │ ├── chat-service.ts
│ │ │ ├── db
│ │ │ │ ├── client.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── schema.ts
│ │ │ ├── directory-picker.ts
│ │ │ ├── engines
│ │ │ │ ├── claude.ts
│ │ │ │ ├── codex.ts
│ │ │ │ └── types.ts
│ │ │ ├── message-service.ts
│ │ │ ├── open-project.ts
│ │ │ ├── project-service.ts
│ │ │ ├── project-types.ts
│ │ │ ├── session-service.ts
│ │ │ ├── storage.ts
│ │ │ ├── stream-manager.ts
│ │ │ ├── tool-bridge.ts
│ │ │ └── types.ts
│ │ ├── cli.ts
│ │ ├── constant
│ │ │ └── index.ts
│ │ ├── file-handler.ts
│ │ ├── index.ts
│ │ ├── mcp
│ │ │ ├── mcp-server-stdio.ts
│ │ │ ├── mcp-server.ts
│ │ │ ├── register-tools.ts
│ │ │ └── stdio-config.json
│ │ ├── native-messaging-host.ts
│ │ ├── scripts
│ │ │ ├── browser-config.ts
│ │ │ ├── build.ts
│ │ │ ├── constant.ts
│ │ │ ├── doctor.ts
│ │ │ ├── postinstall.ts
│ │ │ ├── register-dev.ts
│ │ │ ├── register.ts
│ │ │ ├── report.ts
│ │ │ ├── run_host.bat
│ │ │ ├── run_host.sh
│ │ │ └── utils.ts
│ │ ├── server
│ │ │ ├── index.ts
│ │ │ ├── routes
│ │ │ │ ├── agent.ts
│ │ │ │ └── index.ts
│ │ │ └── server.test.ts
│ │ ├── shims
│ │ │ └── devtools.d.ts
│ │ ├── trace-analyzer.ts
│ │ ├── types
│ │ │ └── devtools-frontend.d.ts
│ │ └── util
│ │ └── logger.ts
│ └── tsconfig.json
├── commitlint.config.cjs
├── docs
│ ├── ARCHITECTURE_zh.md
│ ├── ARCHITECTURE.md
│ ├── CHANGELOG.md
│ ├── CONTRIBUTING_zh.md
│ ├── CONTRIBUTING.md
│ ├── ISSUE.md
│ ├── mcp-cli-config.md
│ ├── TOOLS_zh.md
│ ├── TOOLS.md
│ ├── TROUBLESHOOTING_zh.md
│ ├── TROUBLESHOOTING.md
│ ├── VisualEditor_zh.md
│ ├── VisualEditor.md
│ └── WINDOWS_INSTALL_zh.md
├── eslint.config.js
├── LICENSE
├── package.json
├── packages
│ ├── shared
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── agent-types.ts
│ │ │ ├── constants.ts
│ │ │ ├── index.ts
│ │ │ ├── labels.ts
│ │ │ ├── node-spec-registry.ts
│ │ │ ├── node-spec.ts
│ │ │ ├── node-specs-builtin.ts
│ │ │ ├── rr-graph.ts
│ │ │ ├── step-types.ts
│ │ │ ├── tools.ts
│ │ │ └── types.ts
│ │ └── tsconfig.json
│ └── wasm-simd
│ ├── .gitignore
│ ├── BUILD.md
│ ├── Cargo.toml
│ ├── package.json
│ ├── README.md
│ └── src
│ └── lib.rs
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── prompt
│ ├── content-analize.md
│ ├── excalidraw-prompt.md
│ └── modify-web.md
├── README_zh.md
├── README.md
└── releases
├── chrome-extension
│ └── latest
│ └── chrome-mcp-server-lastest.zip
└── README.md
```
# Files
--------------------------------------------------------------------------------
/app/chrome-extension/entrypoints/web-editor-v2/ui/shadow-host.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Shadow DOM Host
3 | *
4 | * Creates an isolated container for the Web Editor UI using Shadow DOM.
5 | * Provides:
6 | * - Style isolation (no CSS bleed in/out)
7 | * - Event isolation (UI events don't bubble to page)
8 | * - Overlay container for Canvas/visual feedback
9 | * - UI container for panels/controls
10 | */
11 |
12 | import {
13 | WEB_EDITOR_V2_COLORS,
14 | WEB_EDITOR_V2_HOST_ID,
15 | WEB_EDITOR_V2_OVERLAY_ID,
16 | WEB_EDITOR_V2_UI_ID,
17 | WEB_EDITOR_V2_Z_INDEX,
18 | } from '../constants';
19 | import { Disposer } from '../utils/disposables';
20 |
21 | // =============================================================================
22 | // Types
23 | // =============================================================================
24 |
25 | /** Elements exposed by the shadow host */
26 | export interface ShadowHostElements {
27 | /** The host element attached to the document */
28 | host: HTMLDivElement;
29 | /** The shadow root */
30 | shadowRoot: ShadowRoot;
31 | /** Container for overlay elements (Canvas, guides, etc.) */
32 | overlayRoot: HTMLDivElement;
33 | /** Container for UI elements (panels, toolbar, etc.) */
34 | uiRoot: HTMLDivElement;
35 | }
36 |
37 | /** Options for mounting the shadow host (placeholder for future extension) */
38 | export type ShadowHostOptions = Record<string, never>;
39 |
40 | /** Interface for the shadow host manager */
41 | export interface ShadowHostManager {
42 | /** Get the shadow host elements (null if not mounted) */
43 | getElements(): ShadowHostElements | null;
44 | /** Check if a node is part of the editor overlay */
45 | isOverlayElement(node: unknown): boolean;
46 | /** Check if an event originated from the editor UI */
47 | isEventFromUi(event: Event): boolean;
48 | /** Dispose and unmount the shadow host */
49 | dispose(): void;
50 | }
51 |
52 | // =============================================================================
53 | // Styles
54 | // =============================================================================
55 |
56 | const SHADOW_HOST_STYLES = /* css */ `
57 | :host {
58 | all: initial;
59 |
60 | /* Design tokens aligned with attr-ui.html design spec */
61 | /* Surface colors */
62 | --we-surface-bg: #ffffff;
63 | --we-surface-secondary: #fafafa;
64 |
65 | /* Control colors - input containers use gray bg */
66 | --we-control-bg: #f3f3f3;
67 | --we-control-bg-hover: #e8e8e8;
68 | --we-control-border-hover: #e0e0e0;
69 | --we-control-bg-focus: #ffffff;
70 | --we-control-border-focus: #3b82f6;
71 |
72 | /* Border colors */
73 | --we-border-subtle: #e5e5e5;
74 | --we-border-strong: #d4d4d4;
75 | --we-border-section: #f3f3f3;
76 |
77 | /* Text colors */
78 | --we-text-primary: #333333;
79 | --we-text-secondary: #737373;
80 | --we-text-muted: #a3a3a3;
81 |
82 | /* Accent surfaces (used by CSS/Props panels) */
83 | --we-accent-info-bg: rgba(59, 130, 246, 0.08);
84 | --we-accent-brand-bg: rgba(99, 102, 241, 0.12);
85 | --we-accent-brand-border: rgba(99, 102, 241, 0.25);
86 | --we-accent-warning-bg: rgba(251, 191, 36, 0.14);
87 | --we-accent-warning-border: rgba(251, 191, 36, 0.25);
88 | --we-accent-danger-bg: rgba(248, 113, 113, 0.12);
89 | --we-accent-danger-border: rgba(248, 113, 113, 0.25);
90 |
91 | /* Shadows - Tailwind-like shadow-xl */
92 | --we-shadow-subtle: 0 1px 2px rgba(0, 0, 0, 0.05);
93 | --we-shadow-panel: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
94 | --we-shadow-tab: 0 1px 2px rgba(0, 0, 0, 0.05);
95 |
96 | /* Radii */
97 | --we-radius-panel: 8px;
98 | --we-radius-control: 6px;
99 | --we-radius-tab: 4px;
100 |
101 | /* Sizes */
102 | --we-icon-btn-size: 24px;
103 |
104 | /* Focus ring - blue inset border style */
105 | --we-focus-ring: #3b82f6;
106 |
107 | /* Motion - bounce easing for toolbar animations */
108 | --we-ease-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55);
109 | }
110 |
111 | *,
112 | *::before,
113 | *::after {
114 | box-sizing: border-box;
115 | }
116 |
117 | /* Overlay container - for Canvas and visual feedback */
118 | #${WEB_EDITOR_V2_OVERLAY_ID} {
119 | position: fixed;
120 | inset: 0;
121 | pointer-events: none;
122 | contain: layout style;
123 | }
124 |
125 | /* ==========================================================================
126 | * Resize Handles (Phase 4.9)
127 | * ========================================================================== */
128 |
129 | /* Handles layer - covers viewport, pass-through by default */
130 | .we-handles-layer {
131 | position: absolute;
132 | inset: 0;
133 | pointer-events: none;
134 | contain: layout style paint;
135 | }
136 |
137 | /* Selection frame - positioned by selection rect */
138 | .we-selection-frame {
139 | position: absolute;
140 | top: 0;
141 | left: 0;
142 | width: 0;
143 | height: 0;
144 | transform: translate3d(0, 0, 0);
145 | pointer-events: none;
146 | will-change: transform, width, height;
147 | }
148 |
149 | /* Individual resize handle */
150 | .we-resize-handle {
151 | position: absolute;
152 | width: 8px;
153 | height: 8px;
154 | border-radius: 2px;
155 | background: rgba(255, 255, 255, 0.98);
156 | border: 1px solid ${WEB_EDITOR_V2_COLORS.selectionBorder};
157 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
158 | pointer-events: auto;
159 | touch-action: none;
160 | user-select: none;
161 | transition: background-color 0.1s ease, border-color 0.1s ease, transform 0.1s ease;
162 | }
163 |
164 | .we-resize-handle:hover {
165 | background: ${WEB_EDITOR_V2_COLORS.selectionBorder};
166 | border-color: ${WEB_EDITOR_V2_COLORS.selectionBorder};
167 | transform: translate(-50%, -50%) scale(1.15);
168 | }
169 |
170 | .we-resize-handle:active {
171 | transform: translate(-50%, -50%) scale(1.0);
172 | }
173 |
174 | /* Handle positions - all use translate(-50%, -50%) as base */
175 | .we-resize-handle[data-dir="n"] { left: 50%; top: 0; transform: translate(-50%, -50%); cursor: ns-resize; }
176 | .we-resize-handle[data-dir="s"] { left: 50%; top: 100%; transform: translate(-50%, -50%); cursor: ns-resize; }
177 | .we-resize-handle[data-dir="e"] { left: 100%; top: 50%; transform: translate(-50%, -50%); cursor: ew-resize; }
178 | .we-resize-handle[data-dir="w"] { left: 0; top: 50%; transform: translate(-50%, -50%); cursor: ew-resize; }
179 | .we-resize-handle[data-dir="nw"] { left: 0; top: 0; transform: translate(-50%, -50%); cursor: nwse-resize; }
180 | .we-resize-handle[data-dir="ne"] { left: 100%; top: 0; transform: translate(-50%, -50%); cursor: nesw-resize; }
181 | .we-resize-handle[data-dir="sw"] { left: 0; top: 100%; transform: translate(-50%, -50%); cursor: nesw-resize; }
182 | .we-resize-handle[data-dir="se"] { left: 100%; top: 100%; transform: translate(-50%, -50%); cursor: nwse-resize; }
183 |
184 | /* Size HUD - shows W×H while resizing */
185 | .we-size-hud {
186 | position: absolute;
187 | left: 50%;
188 | top: 0;
189 | transform: translate(-50%, calc(-100% - 8px));
190 | padding: 3px 8px;
191 | font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
192 | font-size: 11px;
193 | font-weight: 600;
194 | line-height: 1.2;
195 | color: rgba(255, 255, 255, 0.98);
196 | background: rgba(15, 23, 42, 0.92);
197 | border: 1px solid rgba(51, 65, 85, 0.5);
198 | border-radius: 4px;
199 | pointer-events: none;
200 | user-select: none;
201 | white-space: nowrap;
202 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
203 | backdrop-filter: blur(4px);
204 | -webkit-backdrop-filter: blur(4px);
205 | }
206 |
207 | /* ==========================================================================
208 | * Performance HUD (Phase 5.3)
209 | * ========================================================================== */
210 |
211 | .we-perf-hud {
212 | position: fixed;
213 | left: 12px;
214 | bottom: 12px;
215 | padding: 8px 10px;
216 | border-radius: 10px;
217 | background: rgba(15, 23, 42, 0.78);
218 | border: 1px solid rgba(51, 65, 85, 0.45);
219 | color: rgba(255, 255, 255, 0.96);
220 | font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
221 | font-size: 12px;
222 | line-height: 1.25;
223 | pointer-events: none;
224 | user-select: none;
225 | white-space: nowrap;
226 | z-index: 10;
227 | box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18);
228 | backdrop-filter: blur(6px);
229 | -webkit-backdrop-filter: blur(6px);
230 | font-variant-numeric: tabular-nums;
231 | }
232 |
233 | .we-perf-hud-line + .we-perf-hud-line {
234 | margin-top: 4px;
235 | }
236 |
237 | /* UI container - for panels and controls */
238 | /* Position below toolbar: 16px (toolbar top) + 40px (toolbar height) + 8px (gap) = 64px */
239 | #${WEB_EDITOR_V2_UI_ID} {
240 | position: fixed;
241 | top: 64px;
242 | right: 16px;
243 | pointer-events: auto;
244 | /* Inter font with system fallbacks (aligned with design spec) */
245 | font-family: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
246 | font-size: 11px;
247 | line-height: 1.4;
248 | color: var(--we-text-primary);
249 | -webkit-font-smoothing: antialiased;
250 | }
251 |
252 | /* Panel styles */
253 | /* max-height: 100vh - 64px (top offset) - 16px (bottom margin) = 100vh - 80px */
254 | .we-panel {
255 | width: 280px;
256 | max-width: calc(100vw - 32px);
257 | max-height: calc(100vh - 80px);
258 | background: var(--we-surface-bg);
259 | border: 1px solid var(--we-border-subtle);
260 | border-radius: var(--we-radius-panel);
261 | box-shadow: var(--we-shadow-panel);
262 | overflow: hidden;
263 | contain: layout style paint;
264 | }
265 |
266 | .we-header {
267 | display: flex;
268 | align-items: center;
269 | justify-content: space-between;
270 | gap: 8px;
271 | padding: 8px 12px;
272 | background: var(--we-surface-bg);
273 | border-bottom: 1px solid var(--we-border-subtle);
274 | user-select: none;
275 | }
276 |
277 | .we-title {
278 | display: flex;
279 | align-items: center;
280 | gap: 6px;
281 | font-size: 11px;
282 | font-weight: 600;
283 | color: var(--we-text-primary);
284 | }
285 |
286 | .we-badge {
287 | font-size: 10px;
288 | font-weight: 500;
289 | padding: 2px 6px;
290 | background: linear-gradient(135deg, #6366f1, #8b5cf6);
291 | color: white;
292 | border-radius: 4px;
293 | }
294 |
295 | .we-btn {
296 | display: inline-flex;
297 | align-items: center;
298 | justify-content: center;
299 | gap: 6px;
300 | padding: 6px 12px;
301 | font-size: 12px;
302 | font-weight: 500;
303 | color: #475569;
304 | background: white;
305 | border: 1px solid rgba(148, 163, 184, 0.5);
306 | border-radius: 6px;
307 | cursor: pointer;
308 | transition: all 0.15s ease;
309 | }
310 |
311 | .we-btn:hover {
312 | background: #f8fafc;
313 | border-color: rgba(148, 163, 184, 0.7);
314 | }
315 |
316 | .we-btn:active {
317 | background: #f1f5f9;
318 | }
319 |
320 | .we-btn:focus-visible {
321 | outline: none;
322 | box-shadow: 0 0 0 2px var(--we-focus-ring);
323 | }
324 |
325 | .we-btn:disabled {
326 | opacity: 0.55;
327 | cursor: not-allowed;
328 | }
329 |
330 | .we-btn--primary {
331 | background: linear-gradient(135deg, #0f172a, #1e293b);
332 | color: #ffffff;
333 | border-color: rgba(15, 23, 42, 0.5);
334 | }
335 |
336 | .we-btn--primary:hover:not(:disabled) {
337 | background: linear-gradient(135deg, #1e293b, #334155);
338 | border-color: rgba(15, 23, 42, 0.65);
339 | }
340 |
341 | .we-btn--danger {
342 | color: #b91c1c;
343 | border-color: rgba(248, 113, 113, 0.45);
344 | }
345 |
346 | .we-btn--danger:hover:not(:disabled) {
347 | background: rgba(248, 113, 113, 0.08);
348 | border-color: rgba(248, 113, 113, 0.6);
349 | }
350 |
351 | /* Icon button (28x28) - used for window controls (close/minimize, etc.) */
352 | .we-icon-btn {
353 | width: var(--we-icon-btn-size);
354 | height: var(--we-icon-btn-size);
355 | display: inline-flex;
356 | align-items: center;
357 | justify-content: center;
358 | padding: 0;
359 | background: var(--we-control-bg);
360 | border: 0;
361 | border-radius: var(--we-radius-control);
362 | color: var(--we-text-secondary);
363 | cursor: pointer;
364 | transition: background 0.15s ease, box-shadow 0.15s ease;
365 | }
366 |
367 | .we-icon-btn:hover {
368 | background: var(--we-control-bg-hover);
369 | color: var(--we-text-primary);
370 | }
371 |
372 | .we-icon-btn:active {
373 | background: var(--we-control-bg-hover);
374 | }
375 |
376 | .we-icon-btn:focus-visible {
377 | outline: none;
378 | box-shadow: 0 0 0 2px var(--we-focus-ring);
379 | }
380 |
381 | .we-icon-btn svg {
382 | width: 16px;
383 | height: 16px;
384 | display: block;
385 | }
386 |
387 | /* Drag handle (grip) - used for repositioning floating UI */
388 | .we-drag-handle {
389 | width: var(--we-icon-btn-size);
390 | height: var(--we-icon-btn-size);
391 | display: inline-flex;
392 | align-items: center;
393 | justify-content: center;
394 | flex-shrink: 0;
395 | padding: 0;
396 | background: transparent;
397 | border: 0;
398 | border-radius: var(--we-radius-control);
399 | color: var(--we-text-muted);
400 | cursor: grab;
401 | touch-action: none;
402 | user-select: none;
403 | transition: background 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
404 | }
405 |
406 | .we-drag-handle:hover {
407 | background: var(--we-control-bg);
408 | color: var(--we-text-secondary);
409 | }
410 |
411 | .we-drag-handle:focus-visible {
412 | outline: none;
413 | box-shadow: 0 0 0 2px var(--we-focus-ring);
414 | }
415 |
416 | .we-drag-handle:active,
417 | .we-drag-handle[data-dragging="true"] {
418 | cursor: grabbing;
419 | background: var(--we-control-bg-hover);
420 | color: var(--we-text-primary);
421 | }
422 |
423 | .we-drag-handle svg {
424 | width: 14px;
425 | height: 14px;
426 | display: block;
427 | }
428 |
429 | /* ==========================================================================
430 | * Toolbar (Redesigned per toolbar-ui.html design spec)
431 | * - Bounce easing animations
432 | * - Collapsible pill (580x40 <-> 40x40)
433 | * - Grip icon rotation on collapse
434 | * ========================================================================== */
435 |
436 | .we-toolbar {
437 | position: fixed;
438 | left: 50%;
439 | top: 16px;
440 | transform: translateX(-50%);
441 | width: 580px;
442 | height: 40px;
443 | max-width: calc(100vw - 32px);
444 | display: flex;
445 | align-items: center;
446 | background: #ffffff;
447 | border-radius: 999px;
448 | box-shadow: 0 -4px 10px -6px rgba(15, 23, 42, 0.18),
449 | 0 10px 15px -3px rgba(203, 213, 225, 0.5),
450 | 0 4px 6px -4px rgba(203, 213, 225, 0.5);
451 | pointer-events: auto;
452 | user-select: none;
453 | font-family: 'Inter', system-ui, -apple-system, sans-serif;
454 | font-size: 11px;
455 | color: #475569;
456 | transition: width 500ms var(--we-ease-bounce), height 500ms var(--we-ease-bounce);
457 | overflow: visible;
458 | will-change: width, height;
459 | }
460 |
461 | .we-toolbar[data-position="bottom"] {
462 | top: auto;
463 | bottom: 16px;
464 | }
465 |
466 | /* Dragged toolbar: use left/top (inline styles) instead of docked centering */
467 | .we-toolbar[data-dragged="true"] {
468 | left: auto;
469 | right: auto;
470 | top: auto;
471 | bottom: auto;
472 | transform: none;
473 | }
474 |
475 | /* Collapsed toolbar - 40x40 circle */
476 | .we-toolbar[data-minimized="true"] {
477 | width: 40px;
478 | height: 40px;
479 | }
480 |
481 | /* Toolbar content row (collapses with toolbar) */
482 | .we-toolbar-content {
483 | display: flex;
484 | align-items: center;
485 | flex: 1;
486 | min-width: 0;
487 | gap: 10px;
488 | white-space: nowrap;
489 | padding-right: 8px;
490 | transition: opacity 350ms ease, transform 400ms var(--we-ease-bounce);
491 | will-change: opacity, transform;
492 | }
493 |
494 | .we-toolbar[data-minimized="true"] .we-toolbar-content {
495 | opacity: 0;
496 | transform: translateX(-16px) scale(0.95);
497 | pointer-events: none;
498 | }
499 |
500 | .we-toolbar[data-minimized="false"] .we-toolbar-content {
501 | opacity: 1;
502 | transform: translateX(0) scale(1);
503 | pointer-events: auto;
504 | }
505 |
506 | /* Grip toggle button (40x40, hover slate-50, active scale-90) */
507 | .we-toolbar .we-drag-handle {
508 | width: 40px;
509 | height: 40px;
510 | flex-shrink: 0;
511 | border-radius: 999px;
512 | cursor: pointer;
513 | transition: background-color 150ms ease, transform 150ms ease;
514 | }
515 |
516 | .we-toolbar .we-drag-handle:hover {
517 | background: #f8fafc;
518 | }
519 |
520 | .we-toolbar .we-drag-handle:active {
521 | transform: scale(0.9);
522 | }
523 |
524 | /* Grip icon rotation (collapsed 90deg -> expanded 0deg) */
525 | .we-toolbar .we-drag-handle svg {
526 | width: 16px;
527 | height: 16px;
528 | color: #94a3b8;
529 | transition: transform 500ms var(--we-ease-bounce);
530 | transform: rotate(0deg);
531 | }
532 |
533 | .we-toolbar[data-minimized="true"] .we-drag-handle svg {
534 | transform: rotate(90deg);
535 | }
536 |
537 | /* Status indicator: green dot + "Editor" label */
538 | .we-toolbar-indicator {
539 | display: inline-flex;
540 | align-items: center;
541 | gap: 6px;
542 | padding: 4px 10px;
543 | background: #f1f5f9;
544 | border-radius: 999px;
545 | }
546 |
547 | .we-toolbar-indicator-dot {
548 | width: 6px;
549 | height: 6px;
550 | border-radius: 999px;
551 | background: #10b981;
552 | }
553 |
554 | .we-toolbar-indicator-label {
555 | font-size: 11px;
556 | font-weight: 700;
557 | color: #334155;
558 | letter-spacing: 0.04em;
559 | }
560 |
561 | /* Status-driven dot color + pulse */
562 | .we-toolbar[data-status="progress"] .we-toolbar-indicator-dot {
563 | background: #6366f1;
564 | animation: we-toolbar-dot-pulse 1.5s ease-in-out infinite;
565 | }
566 |
567 | .we-toolbar[data-status="success"] .we-toolbar-indicator-dot {
568 | background: #10b981;
569 | }
570 |
571 | .we-toolbar[data-status="error"] .we-toolbar-indicator-dot {
572 | background: #ef4444;
573 | }
574 |
575 | @keyframes we-toolbar-dot-pulse {
576 | 0%, 100% { opacity: 1; }
577 | 50% { opacity: 0.55; }
578 | }
579 |
580 | /* Undo/Redo counts */
581 | .we-toolbar-history {
582 | display: flex;
583 | gap: 10px;
584 | font-size: 10px;
585 | font-weight: 500;
586 | color: #94a3b8;
587 | font-variant-numeric: tabular-nums;
588 | }
589 |
590 | .we-toolbar-history-value {
591 | color: #475569;
592 | font-weight: 700;
593 | }
594 |
595 | /* Divider */
596 | .we-toolbar-divider {
597 | width: 1px;
598 | height: 16px;
599 | background: #e2e8f0;
600 | }
601 |
602 | /* Structure group (Structure button + divider + Undo/Redo icons) */
603 | .we-toolbar-structure-group {
604 | display: inline-flex;
605 | align-items: center;
606 | background: #f1f5f9;
607 | border-radius: 999px;
608 | padding: 2px;
609 | }
610 |
611 | .we-toolbar-structure-btn {
612 | display: inline-flex;
613 | align-items: center;
614 | gap: 4px;
615 | padding: 4px 10px;
616 | font-size: 11px;
617 | font-weight: 500;
618 | color: #64748b;
619 | background: transparent;
620 | border: 0;
621 | border-radius: 999px;
622 | cursor: pointer;
623 | transition: color 150ms ease, background-color 150ms ease;
624 | }
625 |
626 | .we-toolbar-structure-btn:hover:not(:disabled) {
627 | color: #1e293b;
628 | background: #ffffff;
629 | }
630 |
631 | .we-toolbar-structure-btn:disabled {
632 | opacity: 0.55;
633 | cursor: not-allowed;
634 | }
635 |
636 | .we-toolbar-structure-btn svg {
637 | width: 10px;
638 | height: 10px;
639 | opacity: 0.5;
640 | display: block;
641 | }
642 |
643 | .we-toolbar-structure-separator {
644 | width: 1px;
645 | height: 12px;
646 | background: #e2e8f0;
647 | }
648 |
649 | .we-toolbar-group-icon-btn {
650 | display: inline-flex;
651 | align-items: center;
652 | justify-content: center;
653 | padding: 4px;
654 | background: transparent;
655 | border: 0;
656 | border-radius: 999px;
657 | color: #94a3b8;
658 | cursor: pointer;
659 | transition: color 150ms ease, background-color 150ms ease;
660 | }
661 |
662 | .we-toolbar-group-icon-btn:hover:not(:disabled) {
663 | color: #334155;
664 | background: #ffffff;
665 | }
666 |
667 | .we-toolbar-group-icon-btn:disabled {
668 | opacity: 0.45;
669 | cursor: not-allowed;
670 | }
671 |
672 | .we-toolbar-group-icon-btn svg {
673 | width: 14px;
674 | height: 14px;
675 | display: block;
676 | }
677 |
678 | /* End actions container: pushes apply + close to far right */
679 | .we-toolbar-end-actions {
680 | margin-left: auto;
681 | display: inline-flex;
682 | align-items: center;
683 | gap: 10px;
684 | }
685 |
686 | /* Apply button (indigo-500) */
687 | .we-toolbar-apply-btn {
688 | display: inline-flex;
689 | align-items: center;
690 | justify-content: center;
691 | padding: 6px 16px;
692 | font-size: 11px;
693 | font-weight: 700;
694 | color: #ffffff;
695 | background: #6366f1;
696 | border: 0;
697 | border-radius: 999px;
698 | cursor: pointer;
699 | transition: background-color 150ms ease, transform 150ms ease, box-shadow 150ms ease;
700 | box-shadow: 0 4px 6px -1px rgba(99, 102, 241, 0.25),
701 | 0 2px 4px -2px rgba(99, 102, 241, 0.25);
702 | }
703 |
704 | .we-toolbar-apply-btn:hover:not(:disabled) {
705 | background: #4f46e5;
706 | }
707 |
708 | .we-toolbar-apply-btn:active:not(:disabled) {
709 | transform: scale(0.95);
710 | }
711 |
712 | .we-toolbar-apply-btn:disabled {
713 | opacity: 0.55;
714 | cursor: not-allowed;
715 | box-shadow: none;
716 | }
717 |
718 | /* Close button (red hover) */
719 | .we-toolbar-close-btn {
720 | display: inline-flex;
721 | align-items: center;
722 | justify-content: center;
723 | padding: 6px;
724 | background: transparent;
725 | border: 0;
726 | border-radius: 999px;
727 | color: #94a3b8;
728 | cursor: pointer;
729 | transition: color 150ms ease, background-color 150ms ease;
730 | }
731 |
732 | .we-toolbar-close-btn:hover:not(:disabled) {
733 | color: #ef4444;
734 | background: #fef2f2;
735 | }
736 |
737 | .we-toolbar-close-btn:disabled {
738 | opacity: 0.45;
739 | cursor: not-allowed;
740 | }
741 |
742 | .we-toolbar-close-btn svg {
743 | width: 14px;
744 | height: 14px;
745 | display: block;
746 | }
747 |
748 | /* Screen-reader-only utility */
749 | .we-sr-only {
750 | position: absolute;
751 | width: 1px;
752 | height: 1px;
753 | padding: 0;
754 | margin: -1px;
755 | overflow: hidden;
756 | clip: rect(0, 0, 0, 0);
757 | white-space: nowrap;
758 | border: 0;
759 | }
760 |
761 | /* Respect reduced motion preference */
762 | @media (prefers-reduced-motion: reduce) {
763 | .we-toolbar,
764 | .we-toolbar-content,
765 | .we-toolbar .we-drag-handle,
766 | .we-toolbar .we-drag-handle svg,
767 | .we-toolbar-structure-btn,
768 | .we-toolbar-group-icon-btn,
769 | .we-toolbar-apply-btn,
770 | .we-toolbar-close-btn {
771 | transition: none;
772 | }
773 | }
774 |
775 | /* ==========================================================================
776 | Breadcrumbs (Phase 2.2) - Anchored to selection element
777 | ========================================================================== */
778 | .we-breadcrumbs {
779 | position: fixed;
780 | /* left/top set dynamically via JS based on selection rect */
781 | left: 16px;
782 | top: 72px;
783 | width: auto;
784 | max-width: min(600px, calc(100vw - 400px));
785 | display: flex;
786 | align-items: center;
787 | gap: 2px;
788 | padding: 6px 12px;
789 | background: #5494D7;
790 | border: none;
791 | border-radius: 0;
792 | box-shadow: 0 2px 8px rgba(84, 148, 215, 0.3);
793 | pointer-events: auto;
794 | user-select: none;
795 | overflow-x: auto;
796 | white-space: nowrap;
797 | scrollbar-width: none;
798 | z-index: 5;
799 | }
800 |
801 | .we-breadcrumbs[data-hidden="true"] {
802 | display: none;
803 | }
804 |
805 | .we-breadcrumbs[data-position="bottom"] {
806 | top: auto;
807 | bottom: 72px;
808 | }
809 |
810 | .we-breadcrumbs::-webkit-scrollbar {
811 | display: none;
812 | }
813 |
814 | .we-crumb {
815 | display: inline-flex;
816 | align-items: center;
817 | max-width: 220px;
818 | padding: 2px 6px;
819 | border-radius: 3px;
820 | border: none;
821 | background: transparent;
822 | color: #ffffff;
823 | font-size: 12px;
824 | font-weight: 500;
825 | line-height: 1.2;
826 | cursor: pointer;
827 | overflow: hidden;
828 | text-overflow: ellipsis;
829 | transition: background 0.15s ease;
830 | }
831 |
832 | .we-crumb:hover {
833 | background: rgba(255, 255, 255, 0.15);
834 | }
835 |
836 | .we-crumb:active {
837 | background: rgba(255, 255, 255, 0.25);
838 | }
839 |
840 | .we-crumb:focus-visible {
841 | outline: none;
842 | box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.5);
843 | }
844 |
845 | .we-crumb--current {
846 | background: rgba(255, 255, 255, 0.2);
847 | }
848 |
849 | .we-crumb-sep {
850 | display: inline-flex;
851 | align-items: center;
852 | justify-content: center;
853 | width: 14px;
854 | flex: 0 0 auto;
855 | color: rgba(255, 255, 255, 0.7);
856 | font-size: 12px;
857 | }
858 |
859 | .we-crumb-sep--shadow {
860 | color: rgba(255, 255, 255, 0.9);
861 | }
862 |
863 | .we-body {
864 | padding: 14px;
865 | color: #475569;
866 | font-size: 12px;
867 | }
868 |
869 | .we-status {
870 | display: flex;
871 | align-items: center;
872 | gap: 8px;
873 | padding: 8px 12px;
874 | background: rgba(34, 197, 94, 0.1);
875 | border-radius: 6px;
876 | color: #15803d;
877 | font-size: 12px;
878 | }
879 |
880 | .we-status-dot {
881 | width: 8px;
882 | height: 8px;
883 | background: #22c55e;
884 | border-radius: 50%;
885 | animation: pulse 2s ease-in-out infinite;
886 | }
887 |
888 | @keyframes pulse {
889 | 0%, 100% { opacity: 1; }
890 | 50% { opacity: 0.5; }
891 | }
892 |
893 | /* ==========================================================================
894 | Property Panel (Phase 3)
895 | ========================================================================== */
896 |
897 | .we-prop-panel {
898 | display: flex;
899 | flex-direction: column;
900 | max-height: calc(100vh - 80px);
901 | }
902 |
903 | /* Dragged property panel: becomes a floating fixed panel positioned via left/top (inline styles) */
904 | .we-prop-panel[data-dragged="true"][data-minimized="false"] {
905 | position: fixed;
906 | left: auto;
907 | right: auto;
908 | top: auto;
909 | bottom: auto;
910 | }
911 |
912 | /* Minimized property panel - becomes a small icon button fixed at top-right */
913 | .we-prop-panel[data-minimized="true"] {
914 | position: fixed;
915 | top: 16px;
916 | right: 16px;
917 | width: auto;
918 | max-height: none;
919 | background: transparent;
920 | border: 0;
921 | box-shadow: none;
922 | overflow: visible;
923 | z-index: 10;
924 | }
925 |
926 | .we-prop-panel[data-minimized="true"] .we-header {
927 | padding: 0;
928 | background: transparent;
929 | border-bottom: 0;
930 | }
931 |
932 | /* Symmetric header layout: drag (left) | tabs (center) | minimize (right) */
933 | .we-prop-panel .we-header {
934 | padding: 8px;
935 | gap: 4px;
936 | }
937 |
938 | .we-prop-panel .we-header .we-prop-tabs {
939 | flex: 1;
940 | justify-content: center;
941 | }
942 |
943 | /* Minimize button chevron rotation */
944 | .we-minimize-btn svg {
945 | transition: transform 200ms ease;
946 | }
947 |
948 | .we-prop-panel[data-minimized="true"] .we-minimize-btn svg {
949 | transform: rotate(180deg);
950 | }
951 |
952 | /* Header tooltips: show below to avoid being clipped by panel overflow */
953 | .we-prop-panel .we-header [data-tooltip]::after {
954 | bottom: auto;
955 | top: calc(100% + 6px);
956 | }
957 |
958 | .we-prop-panel .we-header [data-tooltip]::before {
959 | bottom: auto;
960 | top: calc(100% + 2px);
961 | border-top-color: transparent;
962 | border-bottom-color: var(--we-text-primary);
963 | }
964 |
965 | /* Tab container with pill/segmented style (aligned with design spec) */
966 | .we-prop-tabs {
967 | display: inline-flex;
968 | align-items: center;
969 | gap: 2px;
970 | padding: 2px;
971 | background: var(--we-control-bg);
972 | border-radius: var(--we-radius-tab);
973 | }
974 |
975 | .we-tab {
976 | border: 0;
977 | background: transparent;
978 | color: var(--we-text-secondary);
979 | padding: 4px 10px;
980 | border-radius: var(--we-radius-tab);
981 | cursor: pointer;
982 | font-size: 12px;
983 | font-weight: 500;
984 | transition: all 0.1s ease;
985 | }
986 |
987 | .we-tab:hover {
988 | color: var(--we-text-primary);
989 | }
990 |
991 | .we-tab:focus-visible {
992 | outline: none;
993 | box-shadow: inset 0 0 0 2px var(--we-focus-ring);
994 | }
995 |
996 | /* Active tab: white background with subtle shadow */
997 | .we-tab[aria-selected="true"] {
998 | background: var(--we-surface-bg);
999 | color: var(--we-text-primary);
1000 | box-shadow: var(--we-shadow-tab);
1001 | }
1002 |
1003 | .we-prop-body {
1004 | flex: 1;
1005 | overflow-y: auto;
1006 | overflow-x: hidden;
1007 | padding: 12px;
1008 | padding-bottom: 80px; /* Extra space for scrolling (design spec: pb-20) */
1009 | display: flex;
1010 | flex-direction: column;
1011 | gap: 12px;
1012 | scrollbar-width: none; /* Firefox */
1013 | -ms-overflow-style: none; /* IE 10+ */
1014 | }
1015 |
1016 | /* Hide scrollbar for webkit browsers */
1017 | .we-prop-body::-webkit-scrollbar {
1018 | width: 0;
1019 | height: 0;
1020 | }
1021 |
1022 | /* Force hidden state for property panel sections during minimization */
1023 | .we-prop-body[hidden],
1024 | .we-prop-tabs[hidden] {
1025 | display: none;
1026 | }
1027 |
1028 | .we-prop-tab-content {
1029 | display: flex;
1030 | flex-direction: column;
1031 | gap: 10px;
1032 | }
1033 |
1034 | .we-prop-tab-content[hidden] {
1035 | display: none;
1036 | }
1037 |
1038 | .we-prop-empty {
1039 | display: flex;
1040 | align-items: center;
1041 | justify-content: center;
1042 | padding: 24px 12px;
1043 | color: #64748b;
1044 | font-size: 12px;
1045 | text-align: center;
1046 | }
1047 |
1048 | .we-prop-empty[hidden] {
1049 | display: none;
1050 | }
1051 |
1052 | .we-prop-panel[data-empty="true"] .we-prop-tab-content {
1053 | display: none;
1054 | }
1055 |
1056 | /* ==========================================================================
1057 | Components Tree (Phase 3.2)
1058 | ========================================================================== */
1059 |
1060 | .we-tree {
1061 | font-size: 12px;
1062 | line-height: 1.4;
1063 | }
1064 |
1065 | .we-tree-empty {
1066 | padding: 24px 12px;
1067 | color: #64748b;
1068 | text-align: center;
1069 | }
1070 |
1071 | .we-tree-empty[hidden] {
1072 | display: none;
1073 | }
1074 |
1075 | .we-tree-list {
1076 | display: flex;
1077 | flex-direction: column;
1078 | }
1079 |
1080 | .we-tree-list[hidden] {
1081 | display: none;
1082 | }
1083 |
1084 | .we-tree-item {
1085 | display: flex;
1086 | align-items: center;
1087 | gap: 4px;
1088 | padding: 6px 8px;
1089 | cursor: pointer;
1090 | border-radius: 4px;
1091 | transition: background 0.12s;
1092 | color: #475569;
1093 | }
1094 |
1095 | .we-tree-item:hover {
1096 | background: rgba(59, 130, 246, 0.08);
1097 | }
1098 |
1099 | .we-tree-item--selected {
1100 | background: rgba(59, 130, 246, 0.12);
1101 | color: #1d4ed8;
1102 | font-weight: 500;
1103 | }
1104 |
1105 | .we-tree-item--selected:hover {
1106 | background: rgba(59, 130, 246, 0.16);
1107 | }
1108 |
1109 | .we-tree-item--ancestor {
1110 | color: #64748b;
1111 | }
1112 |
1113 | .we-tree-item--child {
1114 | color: #64748b;
1115 | font-size: 11px;
1116 | }
1117 |
1118 | .we-tree-indent {
1119 | color: #94a3b8;
1120 | font-family: monospace;
1121 | user-select: none;
1122 | }
1123 |
1124 | .we-tree-icon {
1125 | flex-shrink: 0;
1126 | color: #94a3b8;
1127 | font-size: 10px;
1128 | }
1129 |
1130 | .we-tree-item--selected .we-tree-icon {
1131 | color: #3b82f6;
1132 | }
1133 |
1134 | .we-tree-label {
1135 | flex: 1;
1136 | min-width: 0;
1137 | overflow: hidden;
1138 | text-overflow: ellipsis;
1139 | white-space: nowrap;
1140 | font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
1141 | }
1142 |
1143 | /* ==========================================================================
1144 | Control Groups (Section style - aligned with design spec)
1145 | Uses separator lines instead of card borders
1146 | ========================================================================== */
1147 |
1148 | .we-group {
1149 | /* No card-style border, use separator lines between sections */
1150 | border: 0;
1151 | border-radius: 0;
1152 | overflow: visible;
1153 | background: transparent;
1154 | }
1155 |
1156 | /* Section separator - top border for non-first groups */
1157 | .we-group + .we-group {
1158 | border-top: 1px solid var(--we-border-section);
1159 | padding-top: 12px;
1160 | margin-top: 4px;
1161 | }
1162 |
1163 | .we-group-header {
1164 | width: 100%;
1165 | display: flex;
1166 | align-items: center;
1167 | justify-content: space-between;
1168 | gap: 6px;
1169 | padding: 0 0 8px 0;
1170 | background: transparent;
1171 | }
1172 |
1173 | .we-group-toggle {
1174 | flex: 1;
1175 | min-width: 0;
1176 | display: flex;
1177 | align-items: center;
1178 | justify-content: space-between;
1179 | gap: 6px;
1180 | padding: 0;
1181 | background: transparent;
1182 | border: 0;
1183 | cursor: pointer;
1184 | color: #333333;
1185 | font-size: 11px;
1186 | font-weight: 600;
1187 | text-align: left;
1188 | transition: color 0.1s ease;
1189 | }
1190 |
1191 | .we-group-toggle:hover {
1192 | color: var(--we-text-primary);
1193 | }
1194 |
1195 | .we-group-toggle:focus-visible {
1196 | outline: none;
1197 | box-shadow: inset 0 0 0 2px var(--we-focus-ring);
1198 | border-radius: 2px;
1199 | }
1200 |
1201 | .we-group-toggle--static {
1202 | cursor: default;
1203 | pointer-events: none;
1204 | }
1205 |
1206 | .we-group-header-actions {
1207 | display: flex;
1208 | align-items: center;
1209 | gap: 2px;
1210 | flex: 0 0 auto;
1211 | }
1212 |
1213 | .we-group-body {
1214 | padding: 0;
1215 | background: transparent;
1216 | border-top: 0;
1217 | }
1218 |
1219 | .we-group[data-collapsed="true"] .we-group-body {
1220 | display: none;
1221 | }
1222 |
1223 | .we-chevron {
1224 | width: 12px;
1225 | height: 12px;
1226 | flex: 0 0 auto;
1227 | color: var(--we-text-muted);
1228 | transition: transform 0.1s ease;
1229 | }
1230 |
1231 | .we-group[data-collapsed="true"] .we-chevron {
1232 | transform: rotate(-90deg);
1233 | }
1234 |
1235 | /* ==========================================================================
1236 | Form Controls (for Design controls)
1237 | ========================================================================== */
1238 |
1239 | /* Field row: vertical stack (label on top, control below) */
1240 | .we-field {
1241 | display: flex;
1242 | flex-direction: column;
1243 | align-items: stretch;
1244 | gap: 4px;
1245 | }
1246 |
1247 | /* Horizontal field variant (label left, control right) */
1248 | .we-field--horizontal {
1249 | flex-direction: row;
1250 | align-items: center;
1251 | gap: 8px;
1252 | }
1253 |
1254 | .we-field-label {
1255 | flex: 0 0 auto;
1256 | width: auto;
1257 | font-size: 10px;
1258 | font-weight: 500;
1259 | color: var(--we-text-secondary);
1260 | }
1261 |
1262 | /* Fixed width label for horizontal layout */
1263 | .we-field--horizontal .we-field-label {
1264 | width: 48px;
1265 | }
1266 |
1267 | .we-field-label--short {
1268 | width: 20px;
1269 | }
1270 |
1271 | /* Hint text (small label above icon groups for H/V distinction) */
1272 | .we-field-hint {
1273 | font-size: 9px;
1274 | color: var(--we-text-muted);
1275 | text-align: center;
1276 | line-height: 1;
1277 | }
1278 |
1279 | /* Content container for complex controls (icon groups, grids, etc.) */
1280 | .we-field-content {
1281 | width: 100%;
1282 | min-width: 0;
1283 | }
1284 |
1285 | /* Input styling aligned with design spec:
1286 | * - Gray background by default
1287 | * - Inset border on hover
1288 | * - White background + blue inset border on focus
1289 | */
1290 | .we-input {
1291 | flex: 1 1 auto;
1292 | flex-shrink: 0; /* Prevent height shrinking in column flex containers */
1293 | min-width: 0;
1294 | height: 28px; /* Design spec: h-[28px] */
1295 | padding: 0 8px;
1296 | font-size: 11px;
1297 | line-height: 26px; /* Ensure vertical centering: 28px - 2px border */
1298 | font-family: inherit;
1299 | color: var(--we-text-primary);
1300 | background: var(--we-control-bg);
1301 | border: 1px solid transparent;
1302 | border-radius: var(--we-radius-control);
1303 | outline: none;
1304 | transition: background 0.1s ease, border-color 0.1s ease, box-shadow 0.1s ease;
1305 | }
1306 |
1307 | .we-input::placeholder {
1308 | color: var(--we-text-muted);
1309 | }
1310 |
1311 | .we-input:hover:not(:focus) {
1312 | border-color: var(--we-control-border-hover);
1313 | }
1314 |
1315 | .we-input:focus {
1316 | background: var(--we-control-bg-focus);
1317 | border-color: var(--we-control-border-focus);
1318 | }
1319 |
1320 | /* ==========================================================================
1321 | * Input Container (Phase 2.1)
1322 | *
1323 | * A wrapper for inputs with prefix/suffix support.
1324 | * Container handles hover/focus-within styling instead of input itself.
1325 | * ========================================================================== */
1326 | .we-input-container {
1327 | min-width: 0;
1328 | display: flex;
1329 | align-items: center;
1330 | height: 28px; /* Design spec: h-[28px] - must be explicit, not flex-controlled */
1331 | flex-shrink: 0; /* Prevent height shrinking in column flex containers */
1332 | padding: 0 8px;
1333 | gap: 4px;
1334 | background: var(--we-control-bg);
1335 | border: 1px solid transparent;
1336 | border-radius: var(--we-radius-control);
1337 | transition: background 0.1s ease, border-color 0.1s ease, box-shadow 0.1s ease;
1338 | }
1339 |
1340 | /* In row flex containers, allow input-container to grow horizontally */
1341 | .we-field-row > .we-input-container,
1342 | .we-radius-control .we-field-row > .we-input-container {
1343 | flex: 1 1 0;
1344 | }
1345 |
1346 | .we-input-container:hover:not(:focus-within) {
1347 | border-color: var(--we-control-border-hover);
1348 | }
1349 |
1350 | .we-input-container:focus-within {
1351 | background: var(--we-control-bg-focus);
1352 | border-color: var(--we-control-border-focus);
1353 | }
1354 |
1355 | .we-input-container__input {
1356 | flex: 1;
1357 | min-width: 0;
1358 | height: 100%;
1359 | padding: 0;
1360 | font-size: 11px;
1361 | line-height: 26px; /* Ensure vertical centering within 28px container */
1362 | font-family: inherit;
1363 | color: var(--we-text-primary);
1364 | background: transparent;
1365 | border: none;
1366 | outline: none;
1367 | }
1368 |
1369 | .we-input-container__input::placeholder {
1370 | color: var(--we-text-muted);
1371 | }
1372 |
1373 | /* Number inputs: right-aligned text in containers */
1374 | .we-input-container__input[inputmode="decimal"],
1375 | .we-input-container__input[inputmode="numeric"] {
1376 | text-align: right;
1377 | }
1378 |
1379 | /* Prefix and suffix elements */
1380 | .we-input-container__prefix,
1381 | .we-input-container__suffix {
1382 | flex: 0 0 auto;
1383 | font-size: 10px;
1384 | color: var(--we-text-muted);
1385 | user-select: none;
1386 | pointer-events: none;
1387 | }
1388 |
1389 | .we-input-container__prefix {
1390 | margin-right: 2px;
1391 | }
1392 |
1393 | .we-input-container__suffix {
1394 | margin-left: 2px;
1395 | }
1396 |
1397 | /* Icon in prefix/suffix */
1398 | .we-input-container__prefix svg,
1399 | .we-input-container__suffix svg {
1400 | width: 12px;
1401 | height: 12px;
1402 | display: block;
1403 | }
1404 |
1405 | /* ==========================================================================
1406 | * Slider Input (Opacity and other numeric ranges)
1407 | * ========================================================================== */
1408 | .we-slider-input {
1409 | display: flex;
1410 | align-items: center;
1411 | gap: 8px;
1412 | width: 100%;
1413 | min-width: 0;
1414 | }
1415 |
1416 | .we-slider-input__slider {
1417 | flex: 1 1 auto;
1418 | min-width: 0;
1419 | height: 28px;
1420 | margin: 0;
1421 | padding: 0;
1422 | background: transparent;
1423 | -webkit-appearance: none;
1424 | appearance: none;
1425 | cursor: pointer;
1426 | }
1427 |
1428 | .we-slider-input__slider:disabled {
1429 | opacity: 0.55;
1430 | cursor: not-allowed;
1431 | }
1432 |
1433 | .we-slider-input__slider::-webkit-slider-runnable-track {
1434 | height: 4px;
1435 | background: linear-gradient(
1436 | to right,
1437 | var(--we-control-border-focus) 0%,
1438 | var(--we-control-border-focus) var(--progress, 0%),
1439 | var(--we-control-bg) var(--progress, 0%),
1440 | var(--we-control-bg) 100%
1441 | );
1442 | border: 1px solid var(--we-control-border-hover);
1443 | border-radius: 999px;
1444 | }
1445 |
1446 | .we-slider-input__slider::-webkit-slider-thumb {
1447 | -webkit-appearance: none;
1448 | appearance: none;
1449 | width: 12px;
1450 | height: 12px;
1451 | margin-top: -5px; /* (12px thumb - 4px track) / 2 + border */
1452 | border-radius: 999px;
1453 | background: var(--we-control-bg-focus);
1454 | border: 1px solid var(--we-border-strong);
1455 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
1456 | }
1457 |
1458 | .we-slider-input__slider:focus-visible {
1459 | outline: none;
1460 | }
1461 |
1462 | .we-slider-input__slider:focus-visible::-webkit-slider-thumb {
1463 | border-color: var(--we-control-border-focus);
1464 | box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.25);
1465 | }
1466 |
1467 | .we-slider-input__slider::-moz-range-track {
1468 | height: 4px;
1469 | background: linear-gradient(
1470 | to right,
1471 | var(--we-control-border-focus) 0%,
1472 | var(--we-control-border-focus) var(--progress, 0%),
1473 | var(--we-control-bg) var(--progress, 0%),
1474 | var(--we-control-bg) 100%
1475 | );
1476 | border: 1px solid var(--we-control-border-hover);
1477 | border-radius: 999px;
1478 | }
1479 |
1480 | .we-slider-input__slider::-moz-range-thumb {
1481 | width: 12px;
1482 | height: 12px;
1483 | border-radius: 999px;
1484 | background: var(--we-control-bg-focus);
1485 | border: 1px solid var(--we-border-strong);
1486 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
1487 | }
1488 |
1489 | .we-slider-input__slider:focus-visible::-moz-range-thumb {
1490 | border-color: var(--we-control-border-focus);
1491 | box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.25);
1492 | }
1493 |
1494 | .we-slider-input__number {
1495 | flex: 0 0 auto;
1496 | }
1497 |
1498 | /* ==========================================================================
1499 | * Icon Button Group (Phase 4.1)
1500 | *
1501 | * A single-select grid of icon buttons (e.g. flex-direction control).
1502 | * ========================================================================== */
1503 | .we-icon-button-group {
1504 | display: grid;
1505 | gap: 4px;
1506 | }
1507 |
1508 | .we-icon-button-group__btn {
1509 | display: flex;
1510 | align-items: center;
1511 | justify-content: center;
1512 | height: 28px; /* Design spec: h-[28px] */
1513 | padding: 4px;
1514 | background: var(--we-control-bg);
1515 | border: 1px solid transparent;
1516 | border-radius: var(--we-radius-control);
1517 | cursor: pointer;
1518 | transition: background-color 0.1s ease, border-color 0.1s ease;
1519 | }
1520 |
1521 | .we-icon-button-group__btn:hover:not(:disabled) {
1522 | background: var(--we-control-bg-hover);
1523 | }
1524 |
1525 | .we-icon-button-group__btn:focus-visible {
1526 | outline: none;
1527 | border-color: var(--we-control-border-focus);
1528 | box-shadow: inset 0 0 0 2px var(--we-control-border-focus); /* Design spec: 2px inset */
1529 | }
1530 |
1531 | .we-icon-button-group__btn[data-selected="true"] {
1532 | background: var(--we-control-bg-focus);
1533 | border-color: var(--we-control-border-focus);
1534 | }
1535 |
1536 | .we-icon-button-group__btn:disabled {
1537 | opacity: 0.5;
1538 | cursor: not-allowed;
1539 | }
1540 |
1541 | .we-icon-button-group__btn svg {
1542 | width: 14px;
1543 | height: 14px;
1544 | color: var(--we-text-secondary);
1545 | }
1546 |
1547 | .we-icon-button-group__btn[data-selected="true"] svg {
1548 | color: var(--we-control-border-focus);
1549 | }
1550 |
1551 | /* ==========================================================================
1552 | * Toggle Button
1553 | *
1554 | * A pressable toggle button (e.g. flip X/Y controls).
1555 | * ========================================================================== */
1556 | .we-toggle-btn {
1557 | display: flex;
1558 | align-items: center;
1559 | justify-content: center;
1560 | width: 28px;
1561 | height: 28px;
1562 | padding: 4px;
1563 | background: var(--we-control-bg);
1564 | border: 1px solid transparent;
1565 | border-radius: var(--we-radius-control);
1566 | cursor: pointer;
1567 | transition: background-color 0.1s ease, border-color 0.1s ease;
1568 | }
1569 |
1570 | .we-toggle-btn:hover:not(:disabled) {
1571 | background: var(--we-control-bg-hover);
1572 | }
1573 |
1574 | .we-toggle-btn[aria-pressed="true"] {
1575 | background: var(--we-control-bg-focus);
1576 | border-color: var(--we-control-border-focus);
1577 | }
1578 |
1579 | .we-toggle-btn:disabled {
1580 | opacity: 0.5;
1581 | cursor: not-allowed;
1582 | }
1583 |
1584 | .we-toggle-btn svg {
1585 | width: 14px;
1586 | height: 14px;
1587 | color: var(--we-text-secondary);
1588 | }
1589 |
1590 | .we-toggle-btn[aria-pressed="true"] svg {
1591 | color: var(--we-control-border-focus);
1592 | }
1593 |
1594 | /* ==========================================================================
1595 | * Alignment Grid (Phase 4.2)
1596 | *
1597 | * 3×3 single-select grid for justify-content + align-items.
1598 | * ========================================================================== */
1599 | .we-alignment-grid {
1600 | display: grid;
1601 | grid-template-columns: repeat(3, 1fr);
1602 | gap: 12px;
1603 | padding: 8px;
1604 | min-height: 90px;
1605 | background: #f9f9f9;
1606 | border: 1px solid #f0f0f0;
1607 | border-radius: var(--we-radius-control);
1608 | place-items: center;
1609 | }
1610 |
1611 | .we-alignment-grid__cell {
1612 | display: flex;
1613 | align-items: center;
1614 | justify-content: center;
1615 | width: 20px;
1616 | height: 20px;
1617 | padding: 0;
1618 | background: transparent;
1619 | border: none;
1620 | border-radius: 2px;
1621 | cursor: pointer;
1622 | transition: background-color 0.1s ease;
1623 | }
1624 |
1625 | .we-alignment-grid__cell:hover:not(:disabled) {
1626 | background: rgba(0, 0, 0, 0.05);
1627 | }
1628 |
1629 | .we-alignment-grid__cell:focus-visible {
1630 | outline: 2px solid var(--we-control-border-focus);
1631 | outline-offset: 1px;
1632 | }
1633 |
1634 | .we-alignment-grid__cell:disabled {
1635 | opacity: 0.5;
1636 | cursor: not-allowed;
1637 | }
1638 |
1639 | /* Inactive dot */
1640 | .we-alignment-grid__dot {
1641 | width: 2px;
1642 | height: 2px;
1643 | background: var(--we-text-muted);
1644 | border-radius: 50%;
1645 | }
1646 |
1647 | /* Active marker (3 bars showing alignment) */
1648 | .we-alignment-grid__marker {
1649 | display: flex;
1650 | flex-direction: column;
1651 | align-items: center;
1652 | justify-content: space-between;
1653 | width: 12px;
1654 | height: 12px;
1655 | }
1656 |
1657 | .we-alignment-grid__bar {
1658 | height: 2px;
1659 | background: var(--we-control-border-focus);
1660 | border-radius: 1px;
1661 | }
1662 |
1663 | .we-alignment-grid__bar--1 { width: 8px; }
1664 | .we-alignment-grid__bar--2 { width: 12px; }
1665 | .we-alignment-grid__bar--3 { width: 4px; }
1666 |
1667 | /* ==========================================================================
1668 | * Grid + Gap Two Column Layout (Layout Control)
1669 | * ========================================================================== */
1670 |
1671 | .we-grid-gap-row {
1672 | display: flex;
1673 | gap: 8px;
1674 | }
1675 |
1676 | .we-grid-gap-col {
1677 | flex: 1;
1678 | min-width: 0;
1679 | }
1680 |
1681 | /* Keep Grid label space for alignment; hide text only when both columns are visible (grid mode) */
1682 | .we-grid-gap-col--grid:not([hidden]):has(+ .we-grid-gap-col--gap:not([hidden])) .we-field-label {
1683 | visibility: hidden;
1684 | }
1685 |
1686 | .we-grid-gap-col .we-field-content {
1687 | width: 100%;
1688 | overflow: visible;
1689 | }
1690 |
1691 | /* Gap inputs vertical layout */
1692 | .we-grid-gap-inputs {
1693 | display: flex;
1694 | flex-direction: column;
1695 | gap: 8px;
1696 | }
1697 |
1698 | /* ==========================================================================
1699 | * Grid Dimensions Picker (Layout Control)
1700 | * ========================================================================== */
1701 |
1702 | .we-grid-dimensions-preview {
1703 | width: 100%;
1704 | height: 64px; /* Match two rows of gap inputs: 28px + 8px gap + 28px */
1705 | display: flex;
1706 | align-items: center;
1707 | justify-content: center;
1708 | gap: 4px;
1709 | font-size: 12px;
1710 | font-family: inherit;
1711 | color: var(--we-text-primary);
1712 | background: var(--we-control-bg);
1713 | border: 1px solid transparent;
1714 | border-radius: var(--we-radius-control);
1715 | cursor: pointer;
1716 | transition: background-color 0.1s ease, border-color 0.1s ease;
1717 | }
1718 |
1719 | .we-grid-dimensions-preview:hover:not(:disabled) {
1720 | background: var(--we-control-bg-hover);
1721 | }
1722 |
1723 | .we-grid-dimensions-preview:disabled {
1724 | opacity: 0.5;
1725 | cursor: not-allowed;
1726 | }
1727 |
1728 | .we-grid-dimensions-popover {
1729 | position: absolute;
1730 | top: calc(100% + 4px);
1731 | left: 0;
1732 | min-width: 220px;
1733 | padding: 10px;
1734 | background: var(--we-surface-bg);
1735 | border: 1px solid var(--we-border-subtle);
1736 | border-radius: 8px;
1737 | box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
1738 | z-index: 60;
1739 | }
1740 |
1741 | .we-grid-dimensions-popover[hidden] {
1742 | display: none;
1743 | }
1744 |
1745 | .we-grid-dimensions-inputs {
1746 | display: flex;
1747 | align-items: center;
1748 | justify-content: center;
1749 | gap: 8px;
1750 | margin-bottom: 10px;
1751 | }
1752 |
1753 | .we-grid-dimensions-times {
1754 | font-size: 12px;
1755 | color: var(--we-text-muted);
1756 | user-select: none;
1757 | }
1758 |
1759 | .we-grid-dimensions-matrix {
1760 | display: grid;
1761 | grid-template-columns: repeat(12, 1fr);
1762 | gap: 3px;
1763 | padding: 6px;
1764 | background: var(--we-surface-secondary);
1765 | border: 1px solid var(--we-border-subtle);
1766 | border-radius: var(--we-radius-control);
1767 | }
1768 |
1769 | .we-grid-dimensions-cell {
1770 | width: 100%;
1771 | aspect-ratio: 1 / 1;
1772 | background: transparent;
1773 | border: 1px solid rgba(0, 0, 0, 0.10);
1774 | border-radius: 2px;
1775 | padding: 0;
1776 | cursor: pointer;
1777 | transition: background-color 0.08s ease, border-color 0.08s ease;
1778 | }
1779 |
1780 | .we-grid-dimensions-cell[data-active="true"] {
1781 | border-color: rgba(59, 130, 246, 0.65);
1782 | background: rgba(59, 130, 246, 0.10);
1783 | }
1784 |
1785 | .we-grid-dimensions-cell[data-selected="true"] {
1786 | border-color: rgba(59, 130, 246, 0.9);
1787 | background: rgba(59, 130, 246, 0.16);
1788 | }
1789 |
1790 | .we-grid-dimensions-tooltip {
1791 | margin-top: 8px;
1792 | text-align: center;
1793 | font-size: 11px;
1794 | color: var(--we-text-secondary);
1795 | }
1796 |
1797 | .we-grid-dimensions-tooltip[hidden] {
1798 | display: none;
1799 | }
1800 |
1801 | .we-input--short {
1802 | width: 56px;
1803 | flex: 0 0 auto;
1804 | }
1805 |
1806 | /* Number inputs: right-aligned text */
1807 | .we-input[type="text"][inputmode="decimal"],
1808 | .we-input[type="number"] {
1809 | text-align: right;
1810 | }
1811 |
1812 | .we-select {
1813 | flex: 1 1 auto;
1814 | flex-shrink: 0; /* Prevent height shrinking in column flex containers */
1815 | min-width: 0;
1816 | height: 28px; /* Design spec: h-[28px] */
1817 | padding: 0 24px 0 8px;
1818 | font-size: 11px;
1819 | line-height: 26px; /* Ensure vertical centering: 28px - 2px border */
1820 | font-family: inherit;
1821 | color: var(--we-text-primary);
1822 | background: var(--we-control-bg) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10'%3E%3Cpath fill='%23737373' d='M2.5 3.5l2.5 3 2.5-3'/%3E%3C/svg%3E") no-repeat right 8px center;
1823 | border: 1px solid transparent;
1824 | border-radius: var(--we-radius-control);
1825 | outline: none;
1826 | -webkit-appearance: none;
1827 | -moz-appearance: none;
1828 | appearance: none;
1829 | cursor: pointer;
1830 | transition: background-color 0.1s ease, border-color 0.1s ease, box-shadow 0.1s ease;
1831 | }
1832 |
1833 | .we-select:hover:not(:focus) {
1834 | border-color: var(--we-control-border-hover);
1835 | }
1836 |
1837 | .we-select:focus {
1838 | background-color: var(--we-control-bg-focus);
1839 | border-color: var(--we-control-border-focus);
1840 | }
1841 |
1842 | /* Field row for multiple inputs side by side */
1843 | .we-field-row {
1844 | display: flex;
1845 | align-items: stretch;
1846 | gap: 8px;
1847 | }
1848 |
1849 | /* Size field with mode select + input stacked vertically */
1850 | .we-size-field {
1851 | display: flex;
1852 | flex-direction: column;
1853 | gap: 4px;
1854 | flex: 1;
1855 | min-width: 0;
1856 | }
1857 |
1858 | .we-size-mode-select {
1859 | width: 100%;
1860 | }
1861 |
1862 | .we-field-group {
1863 | display: flex;
1864 | flex-direction: column;
1865 | gap: 6px;
1866 | }
1867 |
1868 | /* ==========================================================================
1869 | Effects (Box Shadow List)
1870 | ========================================================================== */
1871 |
1872 | .we-effects-toolbar {
1873 | display: flex;
1874 | justify-content: flex-end;
1875 | margin-bottom: 6px;
1876 | }
1877 |
1878 | .we-effects-list {
1879 | display: flex;
1880 | flex-direction: column;
1881 | gap: 6px;
1882 | }
1883 |
1884 | .we-effects-item-wrap {
1885 | position: relative;
1886 | }
1887 |
1888 | .we-effects-item {
1889 | height: 28px;
1890 | display: flex;
1891 | align-items: center;
1892 | gap: 6px;
1893 | padding: 0 6px;
1894 | background: var(--we-control-bg);
1895 | border: 1px solid transparent;
1896 | border-radius: var(--we-radius-control);
1897 | transition: background-color 0.1s ease, border-color 0.1s ease, opacity 0.1s ease;
1898 | }
1899 |
1900 | .we-effects-item:hover {
1901 | background: var(--we-control-bg-hover);
1902 | }
1903 |
1904 | .we-effects-item[data-open="true"] {
1905 | background: var(--we-control-bg-focus);
1906 | border-color: var(--we-control-border-focus);
1907 | }
1908 |
1909 | .we-effects-item[data-enabled="false"] {
1910 | opacity: 0.55;
1911 | }
1912 |
1913 | .we-effects-name {
1914 | flex: 1;
1915 | min-width: 0;
1916 | padding: 0;
1917 | border: 0;
1918 | background: transparent;
1919 | text-align: left;
1920 | font-size: 11px;
1921 | color: var(--we-text-primary);
1922 | cursor: pointer;
1923 | overflow: hidden;
1924 | text-overflow: ellipsis;
1925 | white-space: nowrap;
1926 | }
1927 |
1928 | .we-effects-name:focus-visible {
1929 | outline: none;
1930 | box-shadow: inset 0 0 0 2px var(--we-focus-ring);
1931 | border-radius: 4px;
1932 | }
1933 |
1934 | .we-effects-icon-btn {
1935 | width: 24px;
1936 | height: 24px;
1937 | display: flex;
1938 | align-items: center;
1939 | justify-content: center;
1940 | background: transparent;
1941 | border: 0;
1942 | border-radius: var(--we-radius-control);
1943 | color: var(--we-text-secondary);
1944 | cursor: pointer;
1945 | padding: 0;
1946 | transition: background-color 0.1s ease, color 0.1s ease;
1947 | }
1948 |
1949 | .we-effects-icon-btn:hover:not(:disabled) {
1950 | background: rgba(0, 0, 0, 0.06);
1951 | color: var(--we-text-primary);
1952 | }
1953 |
1954 | .we-effects-icon-btn:focus-visible {
1955 | outline: none;
1956 | box-shadow: 0 0 0 2px var(--we-focus-ring);
1957 | }
1958 |
1959 | .we-effects-icon-btn:disabled {
1960 | opacity: 0.5;
1961 | cursor: not-allowed;
1962 | }
1963 |
1964 | .we-effects-icon-btn svg {
1965 | width: 14px;
1966 | height: 14px;
1967 | }
1968 |
1969 | .we-effects-popover {
1970 | position: absolute;
1971 | top: calc(100% + 6px);
1972 | left: 0;
1973 | width: 220px;
1974 | max-width: 220px;
1975 | padding: 10px;
1976 | background: var(--we-surface-bg);
1977 | border: 1px solid var(--we-border-subtle);
1978 | border-radius: 8px;
1979 | box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
1980 | z-index: 60;
1981 | }
1982 |
1983 | .we-effects-popover[hidden] {
1984 | display: none;
1985 | }
1986 |
1987 | .we-effects-popover-content {
1988 | display: flex;
1989 | flex-direction: column;
1990 | gap: 8px;
1991 | }
1992 |
1993 | /* ==========================================================================
1994 | Gradient Preview Bar (Phase 4B)
1995 | ========================================================================== */
1996 |
1997 | .we-gradient-bar-row {
1998 | width: 100%;
1999 | padding: 4px 0 8px;
2000 | }
2001 |
2002 | .we-gradient-bar {
2003 | position: relative;
2004 | width: 100%;
2005 | height: 60px;
2006 | border-radius: 14px;
2007 | border: 1px solid var(--we-border-subtle);
2008 | background-color: var(--we-control-bg);
2009 | background-image: none; /* set inline by GradientControl */
2010 | box-shadow:
2011 | inset 0 1px 2px rgba(0, 0, 0, 0.08),
2012 | inset 0 0 0 1px rgba(255, 255, 255, 0.5);
2013 | overflow: hidden;
2014 | }
2015 |
2016 | /* Gradient thumbs container */
2017 | .we-gradient-bar-thumbs {
2018 | position: absolute;
2019 | inset: 0;
2020 | pointer-events: none; /* thumbs enable pointer events individually */
2021 | }
2022 |
2023 | /* Gradient thumb (color stop marker) */
2024 | .we-gradient-thumb {
2025 | pointer-events: auto;
2026 | position: absolute;
2027 | top: 50%;
2028 | left: 0;
2029 | transform: translate(-50%, -50%);
2030 | z-index: 1;
2031 | width: 32px;
2032 | height: 32px;
2033 | border-radius: 6px;
2034 | border: 2px solid rgba(255, 255, 255, 0.98);
2035 | background-color: transparent; /* set inline */
2036 | cursor: pointer;
2037 | padding: 0;
2038 | box-sizing: border-box;
2039 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
2040 | touch-action: none;
2041 | user-select: none;
2042 | transition: box-shadow 0.15s ease, z-index 0s;
2043 | }
2044 |
2045 | .we-gradient-thumb:hover {
2046 | box-shadow:
2047 | 0 0 0 2px rgba(59, 130, 246, 0.25),
2048 | 0 1px 3px rgba(0, 0, 0, 0.2);
2049 | }
2050 |
2051 | .we-gradient-thumb:focus-visible {
2052 | outline: none;
2053 | box-shadow:
2054 | 0 0 0 3px rgba(59, 130, 246, 0.4),
2055 | 0 1px 3px rgba(0, 0, 0, 0.2);
2056 | }
2057 |
2058 | /* Selected thumb state - raise above unselected thumbs */
2059 | .we-gradient-thumb--active {
2060 | z-index: 2;
2061 | box-shadow:
2062 | 0 0 0 3px rgba(59, 130, 246, 0.4),
2063 | 0 1px 3px rgba(0, 0, 0, 0.2);
2064 | }
2065 |
2066 | /* Dragging thumb - always on top when overlapping at same position */
2067 | .we-gradient-thumb--dragging {
2068 | z-index: 3;
2069 | }
2070 |
2071 | /* Dragging state - cursor feedback on entire bar */
2072 | .we-gradient-bar--dragging {
2073 | cursor: grabbing;
2074 | }
2075 |
2076 | .we-gradient-bar--dragging .we-gradient-thumb {
2077 | cursor: grabbing;
2078 | }
2079 |
2080 | /* ==========================================================================
2081 | Gradient Stops List (Phase 4D)
2082 | ========================================================================== */
2083 |
2084 | .we-gradient-stops-header {
2085 | display: flex;
2086 | align-items: center;
2087 | justify-content: space-between;
2088 | padding: 6px 0 4px;
2089 | }
2090 |
2091 | .we-gradient-stops-title {
2092 | font-size: 10px;
2093 | font-weight: 600;
2094 | color: var(--we-text-secondary);
2095 | text-transform: uppercase;
2096 | letter-spacing: 0.5px;
2097 | }
2098 |
2099 | .we-gradient-stops-add,
2100 | .we-gradient-stop-remove {
2101 | font-size: 14px;
2102 | font-weight: 500;
2103 | line-height: 1;
2104 | }
2105 |
2106 | .we-gradient-stops-add:disabled,
2107 | .we-gradient-stop-remove:disabled {
2108 | opacity: 0.4;
2109 | cursor: not-allowed;
2110 | }
2111 |
2112 | .we-gradient-stops-list {
2113 | border: 1px solid var(--we-border-subtle);
2114 | border-radius: 12px;
2115 | background: rgba(255, 255, 255, 0.6);
2116 | padding: 6px;
2117 | display: flex;
2118 | flex-direction: column;
2119 | gap: 4px;
2120 | max-height: 180px;
2121 | overflow-y: auto;
2122 | overscroll-behavior: contain;
2123 | }
2124 |
2125 | .we-gradient-stop-row {
2126 | display: flex;
2127 | align-items: center;
2128 | gap: 6px;
2129 | padding: 5px 6px;
2130 | border-radius: 8px;
2131 | border: 1px solid transparent;
2132 | background: rgba(255, 255, 255, 0.85);
2133 | cursor: pointer;
2134 | user-select: none;
2135 | transition: border-color 0.15s ease, background 0.15s ease;
2136 | }
2137 |
2138 | .we-gradient-stop-row:hover {
2139 | background: rgba(59, 130, 246, 0.06);
2140 | }
2141 |
2142 | .we-gradient-stop-row:focus-visible {
2143 | outline: none;
2144 | box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.35);
2145 | }
2146 |
2147 | .we-gradient-stop-row--active {
2148 | border-color: rgba(59, 130, 246, 0.6);
2149 | box-shadow: 0 0 0 1px rgba(59, 130, 246, 0.2);
2150 | }
2151 |
2152 | .we-gradient-stop-pos {
2153 | flex: 0 0 auto;
2154 | min-width: 44px;
2155 | display: flex;
2156 | align-items: center;
2157 | justify-content: flex-end;
2158 | text-align: right;
2159 | font-variant-numeric: tabular-nums;
2160 | font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
2161 | font-size: 11px;
2162 | color: var(--we-text-secondary);
2163 | padding: 3px 6px;
2164 | border-radius: 6px;
2165 | background: var(--we-control-bg);
2166 | cursor: pointer;
2167 | transition: box-shadow 0.15s ease;
2168 | }
2169 |
2170 | .we-gradient-stop-pos:hover {
2171 | background: var(--we-control-bg-hover, var(--we-control-bg));
2172 | }
2173 |
2174 | .we-gradient-stop-pos:focus-within {
2175 | box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.25);
2176 | }
2177 |
2178 | /* Static position display (visible when row is not selected) */
2179 | .we-gradient-stop-pos-static {
2180 | display: block;
2181 | width: 100%;
2182 | text-align: right;
2183 | }
2184 |
2185 | /* Position editor slot (visible when row is selected) */
2186 | .we-gradient-stop-pos-editor {
2187 | display: none;
2188 | width: 100%;
2189 | }
2190 |
2191 | /* Show editor and hide static in active row */
2192 | .we-gradient-stop-row--active .we-gradient-stop-pos-static {
2193 | display: none;
2194 | }
2195 |
2196 | .we-gradient-stop-row--active .we-gradient-stop-pos-editor {
2197 | display: block;
2198 | }
2199 |
2200 | /* Position input styling */
2201 | .we-gradient-stop-pos-input {
2202 | width: 100%;
2203 | border: 0;
2204 | padding: 0;
2205 | margin: 0;
2206 | background: transparent;
2207 | color: inherit;
2208 | font: inherit;
2209 | text-align: right;
2210 | outline: none;
2211 | cursor: text;
2212 | }
2213 |
2214 | .we-gradient-stop-pos-input::placeholder {
2215 | color: var(--we-text-muted);
2216 | }
2217 |
2218 | .we-gradient-stop-color {
2219 | flex: 1;
2220 | min-width: 0;
2221 | display: flex;
2222 | align-items: center;
2223 | gap: 6px;
2224 | padding: 3px 8px;
2225 | border-radius: 6px;
2226 | background: var(--we-control-bg);
2227 | }
2228 |
2229 | /* Static color display (visible when row is not selected) */
2230 | .we-gradient-stop-color-static {
2231 | flex: 1;
2232 | min-width: 0;
2233 | display: flex;
2234 | align-items: center;
2235 | gap: 6px;
2236 | padding: 0;
2237 | border: 0;
2238 | background: transparent;
2239 | color: inherit;
2240 | cursor: pointer;
2241 | text-align: left;
2242 | font-family: inherit;
2243 | }
2244 |
2245 | .we-gradient-stop-color-static:focus-visible {
2246 | outline: none;
2247 | box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.25);
2248 | border-radius: 4px;
2249 | }
2250 |
2251 | /* Color editor slot (visible when row is selected) */
2252 | .we-gradient-stop-color-editor {
2253 | flex: 1;
2254 | min-width: 0;
2255 | display: none;
2256 | }
2257 |
2258 | /* When row is active: hide static, show editor */
2259 | .we-gradient-stop-row--active .we-gradient-stop-color {
2260 | padding: 0;
2261 | background: transparent;
2262 | }
2263 |
2264 | .we-gradient-stop-row--active .we-gradient-stop-color-static {
2265 | display: none;
2266 | }
2267 |
2268 | .we-gradient-stop-row--active .we-gradient-stop-color-editor {
2269 | display: block;
2270 | }
2271 |
2272 | .we-gradient-stop-swatch {
2273 | flex: 0 0 auto;
2274 | width: 14px;
2275 | height: 14px;
2276 | border-radius: 3px;
2277 | border: 1px solid rgba(0, 0, 0, 0.12);
2278 | box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2);
2279 | background: transparent;
2280 | }
2281 |
2282 | .we-gradient-stop-color-text {
2283 | flex: 1;
2284 | min-width: 0;
2285 | font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
2286 | font-size: 11px;
2287 | color: var(--we-text-primary);
2288 | white-space: nowrap;
2289 | overflow: hidden;
2290 | text-overflow: ellipsis;
2291 | }
2292 |
2293 | /* Spacing section (Padding / Margin) */
2294 | .we-spacing-section {
2295 | display: flex;
2296 | flex-direction: column;
2297 | gap: 6px;
2298 | }
2299 |
2300 | .we-spacing-section + .we-spacing-section {
2301 | margin-top: 10px;
2302 | }
2303 |
2304 | .we-spacing-header {
2305 | font-size: 10px;
2306 | font-weight: 600;
2307 | color: #6b7280;
2308 | }
2309 |
2310 | /* Spacing 2x2 grid layout */
2311 | .we-spacing-grid {
2312 | display: grid;
2313 | grid-template-columns: 1fr 1fr;
2314 | gap: 8px;
2315 | }
2316 |
2317 | /* ==========================================================================
2318 | * Border Radius Control
2319 | * ========================================================================== */
2320 |
2321 | .we-radius-control {
2322 | flex: 1;
2323 | min-width: 0;
2324 | display: flex;
2325 | flex-direction: column;
2326 | gap: 8px;
2327 | }
2328 |
2329 | .we-radius-corners-grid {
2330 | display: grid;
2331 | grid-template-columns: 1fr 1fr;
2332 | gap: 8px;
2333 | }
2334 |
2335 | /* ==========================================================================
2336 | CSS Panel (Phase 4.6)
2337 | ========================================================================== */
2338 |
2339 | .we-css-panel {
2340 | font-size: 11px;
2341 | line-height: 1.5;
2342 | }
2343 |
2344 | /* Code-semantic elements use monospace font */
2345 | .we-css-rule-selector,
2346 | .we-css-decl-name,
2347 | .we-css-decl-value,
2348 | .we-css-decl-colon,
2349 | .we-css-decl-semi,
2350 | .we-css-decl-important,
2351 | .we-css-rule-source,
2352 | .we-css-rule-spec {
2353 | font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
2354 | }
2355 |
2356 | /* ==========================================================================
2357 | Class Editor (Phase 4.7)
2358 | ========================================================================== */
2359 |
2360 | .we-css-class-editor-mount {
2361 | margin-bottom: 12px;
2362 | }
2363 |
2364 | .we-class-editor {
2365 | position: relative;
2366 | display: flex;
2367 | flex-wrap: wrap;
2368 | align-items: center;
2369 | gap: 6px;
2370 | padding: 8px 10px;
2371 | border: 1px solid var(--we-border-subtle);
2372 | border-radius: var(--we-radius-panel);
2373 | background: var(--we-surface-bg);
2374 | font-family: system-ui, -apple-system, sans-serif;
2375 | }
2376 |
2377 | .we-class-chips {
2378 | display: flex;
2379 | flex-wrap: wrap;
2380 | gap: 6px;
2381 | flex: 0 1 auto;
2382 | }
2383 |
2384 | .we-class-chip {
2385 | display: inline-flex;
2386 | align-items: center;
2387 | gap: 4px;
2388 | padding: 3px 8px;
2389 | border-radius: 999px;
2390 | background: var(--we-accent-brand-bg);
2391 | border: 1px solid var(--we-accent-brand-border);
2392 | color: #4338ca;
2393 | font-size: 11px;
2394 | line-height: 1.2;
2395 | }
2396 |
2397 | .we-class-chip-text {
2398 | word-break: break-all;
2399 | }
2400 |
2401 | .we-class-chip-remove {
2402 | display: inline-flex;
2403 | align-items: center;
2404 | justify-content: center;
2405 | width: 16px;
2406 | height: 16px;
2407 | padding: 0;
2408 | border: none;
2409 | border-radius: 999px;
2410 | background: transparent;
2411 | color: rgba(67, 56, 202, 0.8);
2412 | cursor: pointer;
2413 | font-size: 14px;
2414 | line-height: 1;
2415 | transition: background-color 0.15s ease;
2416 | }
2417 |
2418 | .we-class-chip-remove:hover {
2419 | background: rgba(99, 102, 241, 0.15);
2420 | color: #4338ca;
2421 | }
2422 |
2423 | .we-class-input {
2424 | flex: 1 1 100px;
2425 | min-width: 80px;
2426 | padding: 5px 8px;
2427 | font-size: 12px;
2428 | border: 1px solid var(--we-border-subtle);
2429 | border-radius: var(--we-radius-control);
2430 | background: var(--we-control-bg-focus);
2431 | outline: none;
2432 | transition: border-color 0.15s ease, box-shadow 0.15s ease;
2433 | }
2434 |
2435 | .we-class-input:focus {
2436 | border-color: var(--we-control-border-focus);
2437 | box-shadow: 0 0 0 2px var(--we-focus-ring);
2438 | }
2439 |
2440 | .we-class-input:disabled {
2441 | opacity: 0.5;
2442 | cursor: not-allowed;
2443 | }
2444 |
2445 | .we-class-input::placeholder {
2446 | color: #94a3b8;
2447 | }
2448 |
2449 | .we-class-suggestions {
2450 | position: absolute;
2451 | top: calc(100% + 4px);
2452 | left: 0;
2453 | right: 0;
2454 | background: var(--we-surface-bg);
2455 | border: 1px solid var(--we-border-subtle);
2456 | border-radius: var(--we-radius-panel);
2457 | box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
2458 | overflow: hidden;
2459 | z-index: 20;
2460 | }
2461 |
2462 | .we-class-suggestions[hidden] {
2463 | display: none;
2464 | }
2465 |
2466 | .we-class-suggestion {
2467 | display: block;
2468 | width: 100%;
2469 | text-align: left;
2470 | padding: 8px 10px;
2471 | border: none;
2472 | background: transparent;
2473 | cursor: pointer;
2474 | font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
2475 | font-size: 11px;
2476 | color: #0f172a;
2477 | transition: background-color 0.1s ease;
2478 | }
2479 |
2480 | .we-class-suggestion:hover {
2481 | background: rgba(99, 102, 241, 0.08);
2482 | }
2483 |
2484 | .we-class-suggestion:focus {
2485 | outline: none;
2486 | background: rgba(99, 102, 241, 0.12);
2487 | }
2488 |
2489 | .we-css-info {
2490 | padding: 8px 10px;
2491 | background: var(--we-accent-info-bg);
2492 | border-radius: var(--we-radius-control);
2493 | color: var(--we-text-secondary);
2494 | font-size: 10px;
2495 | margin-bottom: 8px;
2496 | }
2497 |
2498 | .we-css-info[hidden] {
2499 | display: none;
2500 | }
2501 |
2502 | .we-css-warnings {
2503 | margin-bottom: 8px;
2504 | }
2505 |
2506 | .we-css-warnings[hidden] {
2507 | display: none;
2508 | }
2509 |
2510 | .we-css-warning {
2511 | padding: 6px 10px;
2512 | background: var(--we-accent-warning-bg);
2513 | border: 1px solid var(--we-accent-warning-border);
2514 | border-radius: var(--we-radius-control);
2515 | color: #92400e;
2516 | font-size: 10px;
2517 | margin-bottom: 4px;
2518 | }
2519 |
2520 | .we-css-warning-more {
2521 | padding: 4px 10px;
2522 | color: #92400e;
2523 | font-size: 10px;
2524 | font-style: italic;
2525 | }
2526 |
2527 | .we-css-empty {
2528 | padding: 24px 12px;
2529 | color: #64748b;
2530 | text-align: center;
2531 | font-family: system-ui, sans-serif;
2532 | font-size: 12px;
2533 | }
2534 |
2535 | .we-css-empty[hidden] {
2536 | display: none;
2537 | }
2538 |
2539 | .we-css-sections {
2540 | display: flex;
2541 | flex-direction: column;
2542 | gap: 0;
2543 | }
2544 |
2545 | .we-css-section {
2546 | border: 0;
2547 | border-radius: 0;
2548 | overflow: visible;
2549 | background: transparent;
2550 | }
2551 |
2552 | .we-css-section + .we-css-section {
2553 | border-top: 1px solid var(--we-border-section);
2554 | padding-top: 12px;
2555 | margin-top: 4px;
2556 | }
2557 |
2558 | .we-css-section[data-kind="inherited"] {
2559 | background: transparent;
2560 | }
2561 |
2562 | .we-css-section-header {
2563 | padding: 0 0 8px 0;
2564 | background: transparent;
2565 | border-bottom: 0;
2566 | font-weight: 600;
2567 | color: var(--we-text-primary);
2568 | font-size: 11px;
2569 | text-transform: none;
2570 | letter-spacing: normal;
2571 | }
2572 |
2573 | .we-css-section-rules {
2574 | padding: 0;
2575 | }
2576 |
2577 | /* Flat list style (computed-like view) */
2578 | .we-css-rule {
2579 | margin: 0;
2580 | padding: 0;
2581 | background: transparent;
2582 | border: 0;
2583 | border-radius: 0;
2584 | }
2585 |
2586 | .we-css-rule + .we-css-rule {
2587 | border-top: 1px solid var(--we-border-section);
2588 | padding-top: 10px;
2589 | margin-top: 10px;
2590 | }
2591 |
2592 | .we-css-rule[data-origin="inline"] {
2593 | background: transparent;
2594 | }
2595 |
2596 | .we-css-rule-header {
2597 | display: flex;
2598 | align-items: baseline;
2599 | gap: 8px;
2600 | flex-wrap: nowrap;
2601 | margin-bottom: 8px;
2602 | padding-bottom: 0;
2603 | border-bottom: 0;
2604 | }
2605 |
2606 | .we-css-rule-selector {
2607 | flex: 1 1 auto;
2608 | min-width: 0;
2609 | font-weight: 500;
2610 | color: var(--we-text-secondary);
2611 | white-space: nowrap;
2612 | overflow: hidden;
2613 | text-overflow: ellipsis;
2614 | }
2615 |
2616 | .we-css-rule[data-origin="inline"] .we-css-rule-selector {
2617 | color: #92400e;
2618 | font-style: italic;
2619 | }
2620 |
2621 | .we-css-rule-source {
2622 | flex-shrink: 0;
2623 | color: var(--we-text-muted);
2624 | font-size: 10px;
2625 | margin-left: auto;
2626 | max-width: 45%;
2627 | white-space: nowrap;
2628 | overflow: hidden;
2629 | text-overflow: ellipsis;
2630 | }
2631 |
2632 | .we-css-rule-spec {
2633 | flex-shrink: 0;
2634 | color: var(--we-text-muted);
2635 | font-size: 9px;
2636 | padding: 1px 4px;
2637 | background: var(--we-control-bg);
2638 | border-radius: 3px;
2639 | }
2640 |
2641 | .we-css-decls {
2642 | padding-left: 0;
2643 | }
2644 |
2645 | /* Two-column grid layout for declarations */
2646 | .we-css-decl {
2647 | display: grid;
2648 | grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr);
2649 | column-gap: 12px;
2650 | align-items: baseline;
2651 | padding: 5px 0;
2652 | color: var(--we-text-primary);
2653 | }
2654 |
2655 |
2656 | .we-css-decl[data-status="overridden"] {
2657 | text-decoration: line-through;
2658 | color: var(--we-text-muted);
2659 | }
2660 |
2661 | .we-css-decl-name {
2662 | color: var(--we-text-secondary);
2663 | overflow-wrap: anywhere;
2664 | }
2665 |
2666 | /* Hide punctuation for computed-like view */
2667 | .we-css-decl-colon,
2668 | .we-css-decl-semi {
2669 | display: none;
2670 | }
2671 |
2672 | /* Value container for flex layout with !important */
2673 | .we-css-decl-value-container {
2674 | display: flex;
2675 | align-items: baseline;
2676 | gap: 4px;
2677 | min-width: 0;
2678 | }
2679 |
2680 | .we-css-decl-value {
2681 | color: var(--we-text-primary);
2682 | margin-left: 0;
2683 | min-width: 0;
2684 | white-space: nowrap;
2685 | overflow: hidden;
2686 | text-overflow: ellipsis;
2687 | }
2688 |
2689 | .we-css-decl[data-status="overridden"] .we-css-decl-name,
2690 | .we-css-decl[data-status="overridden"] .we-css-decl-value {
2691 | color: var(--we-text-muted);
2692 | }
2693 |
2694 | .we-css-decl-important {
2695 | flex-shrink: 0;
2696 | color: #dc2626;
2697 | font-weight: 600;
2698 | font-size: 10px;
2699 | }
2700 |
2701 | .we-css-decl[data-status="overridden"] .we-css-decl-important {
2702 | color: #b8c4d0;
2703 | }
2704 |
2705 | /* ==========================================================================
2706 | * Token Picker (Phase 5.4)
2707 | * ========================================================================== */
2708 |
2709 | .we-token-picker {
2710 | position: absolute;
2711 | top: calc(100% + 4px);
2712 | left: 0;
2713 | right: 0;
2714 | background: white;
2715 | border: 1px solid rgba(226, 232, 240, 0.95);
2716 | border-radius: 8px;
2717 | box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
2718 | overflow: hidden;
2719 | z-index: 30;
2720 | }
2721 |
2722 | .we-token-picker[hidden] {
2723 | display: none;
2724 | }
2725 |
2726 | .we-token-filter {
2727 | width: 100%;
2728 | padding: 8px 10px;
2729 | border: none;
2730 | border-bottom: 1px solid rgba(226, 232, 240, 0.8);
2731 | background: transparent;
2732 | font-family: inherit;
2733 | font-size: 11px;
2734 | color: #0f172a;
2735 | outline: none;
2736 | }
2737 |
2738 | .we-token-filter::placeholder {
2739 | color: #94a3b8;
2740 | }
2741 |
2742 | .we-token-toggle-row {
2743 | padding: 6px 10px;
2744 | border-bottom: 1px solid rgba(226, 232, 240, 0.6);
2745 | background: rgba(248, 250, 252, 0.5);
2746 | }
2747 |
2748 | .we-token-toggle-label {
2749 | display: flex;
2750 | align-items: center;
2751 | gap: 6px;
2752 | font-size: 10px;
2753 | color: #64748b;
2754 | cursor: pointer;
2755 | }
2756 |
2757 | .we-token-toggle-checkbox {
2758 | width: 12px;
2759 | height: 12px;
2760 | margin: 0;
2761 | cursor: pointer;
2762 | }
2763 |
2764 | .we-token-list {
2765 | overflow-y: auto;
2766 | overscroll-behavior: contain;
2767 | }
2768 |
2769 | .we-token-list[hidden] {
2770 | display: none;
2771 | }
2772 |
2773 | .we-token-item {
2774 | display: flex;
2775 | align-items: center;
2776 | gap: 8px;
2777 | width: 100%;
2778 | text-align: left;
2779 | padding: 8px 10px;
2780 | border: none;
2781 | background: transparent;
2782 | cursor: pointer;
2783 | transition: background-color 0.1s ease;
2784 | }
2785 |
2786 | .we-token-item:hover {
2787 | background: rgba(99, 102, 241, 0.06);
2788 | }
2789 |
2790 | .we-token-item--selected,
2791 | .we-token-item:focus {
2792 | outline: none;
2793 | background: rgba(99, 102, 241, 0.1);
2794 | }
2795 |
2796 | .we-token-swatch {
2797 | flex-shrink: 0;
2798 | width: 14px;
2799 | height: 14px;
2800 | border-radius: 3px;
2801 | border: 1px solid rgba(0, 0, 0, 0.1);
2802 | box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2);
2803 | }
2804 |
2805 | .we-token-name {
2806 | flex: 1;
2807 | font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
2808 | font-size: 11px;
2809 | color: #0f172a;
2810 | white-space: nowrap;
2811 | overflow: hidden;
2812 | text-overflow: ellipsis;
2813 | }
2814 |
2815 | .we-token-value {
2816 | flex-shrink: 0;
2817 | max-width: 80px;
2818 | font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
2819 | font-size: 10px;
2820 | color: #64748b;
2821 | white-space: nowrap;
2822 | overflow: hidden;
2823 | text-overflow: ellipsis;
2824 | }
2825 |
2826 | .we-token-empty {
2827 | padding: 16px 10px;
2828 | text-align: center;
2829 | color: #94a3b8;
2830 | font-size: 11px;
2831 | }
2832 |
2833 | .we-token-empty[hidden] {
2834 | display: none;
2835 | }
2836 |
2837 | /* Token button for input fields */
2838 | .we-token-btn {
2839 | display: flex;
2840 | align-items: center;
2841 | justify-content: center;
2842 | width: 20px;
2843 | height: 20px;
2844 | padding: 0;
2845 | border: none;
2846 | border-radius: 4px;
2847 | background: rgba(99, 102, 241, 0.08);
2848 | color: #6366f1;
2849 | cursor: pointer;
2850 | transition: background-color 0.15s ease;
2851 | }
2852 |
2853 | .we-token-btn:hover {
2854 | background: rgba(99, 102, 241, 0.15);
2855 | }
2856 |
2857 | .we-token-btn:focus {
2858 | outline: none;
2859 | box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.3);
2860 | }
2861 |
2862 | .we-token-btn-icon {
2863 | width: 12px;
2864 | height: 12px;
2865 | }
2866 |
2867 | /* ==========================================================================
2868 | * Token Pill (Phase 5.3)
2869 | *
2870 | * Compact pill UI for displaying a CSS var() reference in input fields.
2871 | * Used when ColorField value is a standalone var(--token) expression.
2872 | * ========================================================================== */
2873 |
2874 | .we-token-pill {
2875 | flex: 1;
2876 | min-width: 0;
2877 | height: 28px;
2878 | display: flex;
2879 | align-items: center;
2880 | gap: 6px;
2881 | padding: 0 6px 0 4px;
2882 | background: var(--we-control-bg);
2883 | border: 1px solid transparent;
2884 | border-radius: var(--we-radius-control);
2885 | transition: background 0.1s ease, border-color 0.1s ease;
2886 | }
2887 |
2888 | .we-token-pill:hover:not([data-disabled="true"]) {
2889 | border-color: var(--we-control-border-hover);
2890 | }
2891 |
2892 | .we-token-pill:focus-within {
2893 | background: var(--we-control-bg-focus);
2894 | border-color: var(--we-control-border-focus);
2895 | }
2896 |
2897 | .we-token-pill[data-disabled="true"] {
2898 | opacity: 0.5;
2899 | pointer-events: none;
2900 | }
2901 |
2902 | .we-token-pill[hidden] {
2903 | display: none;
2904 | }
2905 |
2906 | /* Leading slot: holds external element (ColorField swatch) or internal swatch */
2907 | .we-token-pill__leading {
2908 | display: flex;
2909 | align-items: center;
2910 | flex: 0 0 auto;
2911 | }
2912 |
2913 | /* Internal swatch (used when no external leading element provided) */
2914 | .we-token-pill__swatch {
2915 | width: 16px;
2916 | height: 16px;
2917 | border-radius: 4px;
2918 | border: 1px solid rgba(0, 0, 0, 0.1);
2919 | background: transparent;
2920 | }
2921 |
2922 | /* Main clickable area */
2923 | .we-token-pill__main {
2924 | flex: 1;
2925 | min-width: 0;
2926 | height: 100%;
2927 | display: flex;
2928 | align-items: center;
2929 | justify-content: space-between;
2930 | gap: 8px;
2931 | padding: 0;
2932 | border: none;
2933 | background: transparent;
2934 | color: var(--we-text-primary);
2935 | cursor: pointer;
2936 | font-size: 11px;
2937 | text-align: left;
2938 | }
2939 |
2940 | .we-token-pill__main:focus {
2941 | outline: none;
2942 | }
2943 |
2944 | .we-token-pill__main:disabled {
2945 | cursor: default;
2946 | }
2947 |
2948 | /* Token name with ellipsis */
2949 | .we-token-pill__name {
2950 | min-width: 0;
2951 | white-space: nowrap;
2952 | overflow: hidden;
2953 | text-overflow: ellipsis;
2954 | font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
2955 | font-size: 11px;
2956 | }
2957 |
2958 | /* Link icon (rotated 45° to indicate variable binding) */
2959 | .we-token-pill__icon {
2960 | width: 14px;
2961 | height: 14px;
2962 | flex: 0 0 auto;
2963 | color: var(--we-text-muted);
2964 | transform: rotate(45deg);
2965 | }
2966 |
2967 | /* Clear button (hover to reveal) */
2968 | .we-token-pill__clear {
2969 | width: 18px;
2970 | height: 18px;
2971 | display: flex;
2972 | align-items: center;
2973 | justify-content: center;
2974 | padding: 0;
2975 | border: none;
2976 | border-radius: 4px;
2977 | background: transparent;
2978 | color: var(--we-text-muted);
2979 | font-size: 14px;
2980 | line-height: 1;
2981 | cursor: pointer;
2982 | opacity: 0;
2983 | pointer-events: none;
2984 | transition: opacity 0.12s ease, background 0.12s ease, color 0.12s ease;
2985 | }
2986 |
2987 | .we-token-pill:hover .we-token-pill__clear {
2988 | opacity: 1;
2989 | pointer-events: auto;
2990 | }
2991 |
2992 | .we-token-pill__clear:hover {
2993 | background: rgba(15, 23, 42, 0.06);
2994 | color: var(--we-text-primary);
2995 | }
2996 |
2997 | .we-token-pill__clear:focus {
2998 | outline: none;
2999 | opacity: 1;
3000 | pointer-events: auto;
3001 | }
3002 |
3003 | .we-token-pill__clear:disabled {
3004 | cursor: default;
3005 | }
3006 |
3007 | /* ==========================================================================
3008 | Props Panel (Phase 7.3)
3009 | ========================================================================== */
3010 |
3011 | .we-props-panel {
3012 | display: flex;
3013 | flex-direction: column;
3014 | gap: 10px;
3015 | }
3016 |
3017 | .we-props-meta {
3018 | padding: 0 0 8px 0;
3019 | display: flex;
3020 | flex-direction: column;
3021 | gap: 6px;
3022 | }
3023 |
3024 | .we-props-meta-title {
3025 | display: flex;
3026 | align-items: center;
3027 | justify-content: space-between;
3028 | gap: 10px;
3029 | color: var(--we-text-primary);
3030 | font-size: 11px;
3031 | font-weight: 600;
3032 | }
3033 |
3034 | .we-props-component {
3035 | min-width: 0;
3036 | overflow: hidden;
3037 | text-overflow: ellipsis;
3038 | white-space: nowrap;
3039 | }
3040 |
3041 | .we-props-badge {
3042 | flex: 0 0 auto;
3043 | font-size: 10px;
3044 | font-weight: 600;
3045 | padding: 2px 6px;
3046 | border-radius: 999px;
3047 | background: var(--we-accent-brand-bg);
3048 | color: #1d4ed8;
3049 | }
3050 |
3051 | .we-props-status {
3052 | font-size: 11px;
3053 | color: #64748b;
3054 | font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
3055 | }
3056 |
3057 | .we-props-warning {
3058 | font-size: 11px;
3059 | color: #92400e;
3060 | background: var(--we-accent-warning-bg);
3061 | border: 1px solid var(--we-accent-warning-border);
3062 | border-radius: var(--we-radius-control);
3063 | padding: 6px 8px;
3064 | }
3065 |
3066 | .we-props-error {
3067 | font-size: 11px;
3068 | color: #b91c1c;
3069 | background: var(--we-accent-danger-bg);
3070 | border: 1px solid var(--we-accent-danger-border);
3071 | border-radius: var(--we-radius-control);
3072 | padding: 6px 8px;
3073 | }
3074 |
3075 | .we-props-source {
3076 | display: flex;
3077 | align-items: center;
3078 | gap: 8px;
3079 | font-size: 11px;
3080 | padding: 4px 0;
3081 | }
3082 |
3083 | .we-props-source[hidden] {
3084 | display: none;
3085 | }
3086 |
3087 | .we-props-source-label {
3088 | flex: 0 0 auto;
3089 | color: #64748b;
3090 | font-weight: 500;
3091 | }
3092 |
3093 | .we-props-source-path {
3094 | flex: 1 1 auto;
3095 | min-width: 0;
3096 | overflow: hidden;
3097 | text-overflow: ellipsis;
3098 | white-space: nowrap;
3099 | color: var(--we-text-primary);
3100 | font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
3101 | }
3102 |
3103 | .we-btn-small {
3104 | padding: 2px 8px;
3105 | font-size: 11px;
3106 | }
3107 |
3108 | /* Source open button - minimal link style */
3109 | .we-props-source-btn {
3110 | flex: 0 0 auto;
3111 | display: inline-flex;
3112 | align-items: center;
3113 | justify-content: center;
3114 | padding: 2px;
3115 | margin-left: 2px;
3116 | border: none;
3117 | background: none;
3118 | color: #64748b;
3119 | cursor: pointer;
3120 | transition: color 0.12s ease;
3121 | }
3122 |
3123 | .we-props-source-btn:hover:not(:disabled) {
3124 | color: #3b82f6;
3125 | }
3126 |
3127 | .we-props-source-btn:disabled {
3128 | opacity: 0.35;
3129 | cursor: not-allowed;
3130 | }
3131 |
3132 | .we-props-source-btn svg {
3133 | display: block;
3134 | }
3135 |
3136 | /* Tooltip - fixed position, mounted at shadow root level */
3137 | .we-tooltip {
3138 | position: fixed;
3139 | transform: translateX(-50%);
3140 | padding: 4px 8px;
3141 | font-size: 11px;
3142 | font-weight: 500;
3143 | line-height: 1.2;
3144 | color: #fff;
3145 | background: rgba(15, 23, 42, 0.92);
3146 | border-radius: 4px;
3147 | white-space: nowrap;
3148 | pointer-events: none;
3149 | z-index: 10000;
3150 | }
3151 |
3152 | .we-tooltip[hidden] {
3153 | display: none;
3154 | }
3155 |
3156 | .we-props-title-left {
3157 | display: flex;
3158 | align-items: center;
3159 | gap: 6px;
3160 | min-width: 0;
3161 | }
3162 |
3163 | .we-props-title-actions {
3164 | display: flex;
3165 | align-items: center;
3166 | gap: 2px;
3167 | margin-left: auto;
3168 | }
3169 |
3170 | /* Action button - minimal icon style for title bar */
3171 | .we-props-action-btn {
3172 | display: inline-flex;
3173 | align-items: center;
3174 | justify-content: center;
3175 | padding: 4px;
3176 | border: none;
3177 | background: none;
3178 | color: var(--we-text-secondary);
3179 | cursor: pointer;
3180 | transition: color 0.12s ease;
3181 | }
3182 |
3183 | .we-props-action-btn:hover:not(:disabled) {
3184 | color: var(--we-text-primary);
3185 | }
3186 |
3187 | .we-props-action-btn:disabled {
3188 | opacity: 0.35;
3189 | cursor: not-allowed;
3190 | }
3191 |
3192 | .we-props-action-btn svg {
3193 | display: block;
3194 | }
3195 |
3196 | .we-props-list {
3197 | overflow: hidden;
3198 | }
3199 |
3200 | .we-props-empty {
3201 | padding: 16px 12px;
3202 | color: #64748b;
3203 | font-size: 12px;
3204 | text-align: center;
3205 | }
3206 |
3207 | .we-props-empty[hidden] {
3208 | display: none;
3209 | }
3210 |
3211 | /* Loading animations */
3212 | @keyframes we-shimmer {
3213 | to {
3214 | background-position: 200% center;
3215 | }
3216 | }
3217 |
3218 | @keyframes we-spin {
3219 | to {
3220 | transform: rotate(360deg);
3221 | }
3222 | }
3223 |
3224 | .we-props-empty.we-loading {
3225 | display: flex;
3226 | align-items: center;
3227 | justify-content: center;
3228 | gap: 8px;
3229 | }
3230 |
3231 | .we-props-empty.we-loading svg {
3232 | flex-shrink: 0;
3233 | color: #94a3b8;
3234 | }
3235 |
3236 | .we-props-empty.we-loading span {
3237 | background: linear-gradient(
3238 | 90deg,
3239 | #64748b 0%,
3240 | #94a3b8 50%,
3241 | #64748b 100%
3242 | );
3243 | background-size: 200% auto;
3244 | color: transparent;
3245 | -webkit-background-clip: text;
3246 | background-clip: text;
3247 | animation: we-shimmer 2s linear infinite;
3248 | }
3249 |
3250 | .we-props-group {
3251 | padding: 0 0 8px 0;
3252 | margin-top: 4px;
3253 | color: var(--we-text-primary);
3254 | font-size: 11px;
3255 | font-weight: 600;
3256 | border-top: 1px solid var(--we-border-section);
3257 | padding-top: 12px;
3258 | }
3259 |
3260 | .we-props-group:first-child {
3261 | border-top: 0;
3262 | margin-top: 0;
3263 | padding-top: 0;
3264 | }
3265 |
3266 | .we-props-group + .we-props-row {
3267 | border-top: 0;
3268 | }
3269 |
3270 | .we-props-rows {
3271 | display: flex;
3272 | flex-direction: column;
3273 | }
3274 |
3275 | .we-props-row {
3276 | display: flex;
3277 | align-items: center;
3278 | gap: 10px;
3279 | padding: 8px 0;
3280 | border-top: 1px solid var(--we-border-section);
3281 | }
3282 |
3283 | .we-props-row:first-child {
3284 | border-top: 0;
3285 | }
3286 |
3287 | .we-props-key {
3288 | flex: 0 0 110px;
3289 | min-width: 0;
3290 | overflow: hidden;
3291 | text-overflow: ellipsis;
3292 | white-space: nowrap;
3293 | font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
3294 | font-size: 11px;
3295 | color: #334155;
3296 | }
3297 |
3298 | .we-props-value {
3299 | flex: 1;
3300 | min-width: 0;
3301 | display: flex;
3302 | align-items: center;
3303 | justify-content: flex-end;
3304 | gap: 8px;
3305 | }
3306 |
3307 | .we-props-value--readonly {
3308 | justify-content: flex-start;
3309 | color: #475569;
3310 | font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
3311 | font-size: 11px;
3312 | white-space: nowrap;
3313 | overflow: hidden;
3314 | text-overflow: ellipsis;
3315 | }
3316 |
3317 | .we-props-input {
3318 | width: 140px;
3319 | max-width: 100%;
3320 | }
3321 |
3322 | .we-props-input--invalid {
3323 | border-color: rgba(248, 113, 113, 0.85);
3324 | }
3325 |
3326 | .we-props-bool {
3327 | display: inline-flex;
3328 | align-items: center;
3329 | gap: 8px;
3330 | font-size: 11px;
3331 | color: #475569;
3332 | cursor: pointer;
3333 | }
3334 |
3335 | .we-props-checkbox {
3336 | width: 14px;
3337 | height: 14px;
3338 | accent-color: #6366f1;
3339 | cursor: pointer;
3340 | }
3341 |
3342 | .we-props-checkbox:disabled {
3343 | cursor: not-allowed;
3344 | opacity: 0.5;
3345 | }
3346 |
3347 | /* ==========================================================================
3348 | * Color Field (Phase 4.4)
3349 | * ========================================================================== */
3350 |
3351 | .we-color-field {
3352 | flex: 1;
3353 | display: flex;
3354 | align-items: center;
3355 | gap: 6px;
3356 | min-width: 0;
3357 | }
3358 |
3359 | .we-color-swatch {
3360 | flex: 0 0 auto;
3361 | width: 24px;
3362 | height: 24px;
3363 | padding: 0;
3364 | position: relative;
3365 | border: 1px solid var(--we-border-subtle);
3366 | border-radius: var(--we-radius-control);
3367 | background: var(--we-control-bg);
3368 | cursor: pointer;
3369 | transition: border-color 0.15s ease, box-shadow 0.15s ease;
3370 | overflow: hidden;
3371 | }
3372 |
3373 | .we-color-swatch:hover {
3374 | border-color: var(--we-border-strong);
3375 | }
3376 |
3377 | .we-color-swatch:focus-visible,
3378 | .we-color-swatch:focus-within {
3379 | outline: none;
3380 | box-shadow: 0 0 0 2px var(--we-focus-ring);
3381 | }
3382 |
3383 | .we-color-swatch:disabled {
3384 | opacity: 0.5;
3385 | cursor: not-allowed;
3386 | }
3387 |
3388 | /* Native color input overlays the swatch for direct click interaction */
3389 | .we-color-native-input {
3390 | position: absolute;
3391 | inset: 0;
3392 | width: 100%;
3393 | height: 100%;
3394 | opacity: 0;
3395 | cursor: pointer;
3396 | border: none;
3397 | padding: 0;
3398 | margin: 0;
3399 | }
3400 |
3401 | .we-color-text {
3402 | flex: 1;
3403 | min-width: 0;
3404 | }
3405 |
3406 | /* ==========================================================================
3407 | * Tooltip (data-tooltip)
3408 | *
3409 | * CSS-only tooltips using the data-tooltip attribute.
3410 | * Shows on hover/focus with minimal delay.
3411 | * ========================================================================== */
3412 |
3413 | [data-tooltip] {
3414 | position: relative;
3415 | }
3416 |
3417 | [data-tooltip]::after {
3418 | content: attr(data-tooltip);
3419 | position: absolute;
3420 | bottom: calc(100% + 6px);
3421 | left: 50%;
3422 | transform: translateX(-50%);
3423 | padding: 4px 8px;
3424 | font-size: 11px;
3425 | font-family: inherit;
3426 | font-weight: 400;
3427 | line-height: 1.3;
3428 | white-space: nowrap;
3429 | color: var(--we-surface-bg);
3430 | background-color: var(--we-text-primary);
3431 | border-radius: var(--we-radius-control);
3432 | opacity: 0;
3433 | visibility: hidden;
3434 | transition:
3435 | opacity 100ms ease,
3436 | visibility 100ms ease;
3437 | pointer-events: none;
3438 | z-index: 99999;
3439 | }
3440 |
3441 | [data-tooltip]::before {
3442 | content: '';
3443 | position: absolute;
3444 | bottom: calc(100% + 2px);
3445 | left: 50%;
3446 | transform: translateX(-50%);
3447 | border: 4px solid transparent;
3448 | border-top-color: var(--we-text-primary);
3449 | opacity: 0;
3450 | visibility: hidden;
3451 | transition:
3452 | opacity 100ms ease,
3453 | visibility 100ms ease;
3454 | pointer-events: none;
3455 | z-index: 99999;
3456 | }
3457 |
3458 | [data-tooltip]:hover::after,
3459 | [data-tooltip]:focus-visible::after,
3460 | [data-tooltip]:focus-within::after {
3461 | opacity: 1;
3462 | visibility: visible;
3463 | }
3464 |
3465 | [data-tooltip]:hover::before,
3466 | [data-tooltip]:focus-visible::before,
3467 | [data-tooltip]:focus-within::before {
3468 | opacity: 1;
3469 | visibility: visible;
3470 | }
3471 |
3472 | /* ==========================================================================
3473 | * Global Hidden Rule
3474 | * Ensures [hidden] attribute always hides elements, even when they have
3475 | * explicit display values (flex, inline-flex, etc.)
3476 | * ========================================================================== */
3477 | [hidden] {
3478 | display: none !important;
3479 | }
3480 | `;
3481 |
3482 | // =============================================================================
3483 | // Implementation
3484 | // =============================================================================
3485 |
3486 | /**
3487 | * Set a CSS property with !important flag
3488 | */
3489 | function setImportantStyle(element: HTMLElement, property: string, value: string): void {
3490 | element.style.setProperty(property, value, 'important');
3491 | }
3492 |
3493 | // Note: The legacy createPanelContent has been replaced by createPropertyPanel (Phase 3)
3494 |
3495 | /**
3496 | * Mount the Shadow DOM host and return a manager interface
3497 | */
3498 | export function mountShadowHost(options: ShadowHostOptions = {}): ShadowHostManager {
3499 | const disposer = new Disposer();
3500 | let elements: ShadowHostElements | null = null;
3501 |
3502 | // Clean up any existing host (from crash/reload)
3503 | const existing = document.getElementById(WEB_EDITOR_V2_HOST_ID);
3504 | if (existing) {
3505 | try {
3506 | existing.remove();
3507 | } catch {
3508 | // Best-effort cleanup
3509 | }
3510 | }
3511 |
3512 | // Create host element
3513 | const host = document.createElement('div');
3514 | host.id = WEB_EDITOR_V2_HOST_ID;
3515 | host.setAttribute('data-mcp-web-editor', 'v2');
3516 |
3517 | // Apply host styles with !important to resist page CSS
3518 | setImportantStyle(host, 'position', 'fixed');
3519 | setImportantStyle(host, 'inset', '0');
3520 | setImportantStyle(host, 'z-index', String(WEB_EDITOR_V2_Z_INDEX));
3521 | setImportantStyle(host, 'pointer-events', 'none');
3522 | setImportantStyle(host, 'contain', 'layout style paint');
3523 | setImportantStyle(host, 'isolation', 'isolate');
3524 |
3525 | // Create shadow root
3526 | const shadowRoot = host.attachShadow({ mode: 'open' });
3527 |
3528 | // Add styles
3529 | const styleEl = document.createElement('style');
3530 | styleEl.textContent = SHADOW_HOST_STYLES;
3531 | shadowRoot.append(styleEl);
3532 |
3533 | // Create overlay container (for Canvas)
3534 | const overlayRoot = document.createElement('div');
3535 | overlayRoot.id = WEB_EDITOR_V2_OVERLAY_ID;
3536 |
3537 | // Create UI container (for panels)
3538 | // Note: Property Panel is now created separately by editor.ts (Phase 3)
3539 | const uiRoot = document.createElement('div');
3540 | uiRoot.id = WEB_EDITOR_V2_UI_ID;
3541 |
3542 | shadowRoot.append(overlayRoot, uiRoot);
3543 |
3544 | // Mount to document
3545 | const mountPoint = document.documentElement ?? document.body;
3546 | mountPoint.append(host);
3547 | disposer.add(() => host.remove());
3548 |
3549 | elements = { host, shadowRoot, overlayRoot, uiRoot };
3550 |
3551 | // Event isolation: prevent UI events from bubbling to page
3552 | const blockedEvents = [
3553 | 'pointerdown',
3554 | 'pointerup',
3555 | 'pointermove',
3556 | 'pointerenter',
3557 | 'pointerleave',
3558 | 'mousedown',
3559 | 'mouseup',
3560 | 'mousemove',
3561 | 'mouseenter',
3562 | 'mouseleave',
3563 | 'click',
3564 | 'dblclick',
3565 | 'contextmenu',
3566 | 'keydown',
3567 | 'keyup',
3568 | 'keypress',
3569 | 'wheel',
3570 | 'touchstart',
3571 | 'touchmove',
3572 | 'touchend',
3573 | 'touchcancel',
3574 | 'focus',
3575 | 'blur',
3576 | 'input',
3577 | 'change',
3578 | ];
3579 |
3580 | const stopPropagation = (event: Event) => {
3581 | event.stopPropagation();
3582 | };
3583 |
3584 | for (const eventType of blockedEvents) {
3585 | disposer.listen(uiRoot, eventType, stopPropagation);
3586 | // Also block overlay interactions (handles, guides) from bubbling to page
3587 | // Note: capture-phase listeners on the page cannot be fully prevented
3588 | disposer.listen(overlayRoot, eventType, stopPropagation);
3589 | }
3590 |
3591 | // Helper: check if a node is part of the editor
3592 | const isOverlayElement = (node: unknown): boolean => {
3593 | if (!(node instanceof Node)) return false;
3594 | if (node === host) return true;
3595 |
3596 | const root = typeof node.getRootNode === 'function' ? node.getRootNode() : null;
3597 | return root instanceof ShadowRoot && root.host === host;
3598 | };
3599 |
3600 | // Helper: check if an event came from the editor UI
3601 | const isEventFromUi = (event: Event): boolean => {
3602 | try {
3603 | if (typeof event.composedPath === 'function') {
3604 | return event.composedPath().some((el) => isOverlayElement(el));
3605 | }
3606 | } catch {
3607 | // Fallback to target
3608 | }
3609 | return isOverlayElement(event.target);
3610 | };
3611 |
3612 | return {
3613 | getElements: () => elements,
3614 | isOverlayElement,
3615 | isEventFromUi,
3616 | dispose: () => {
3617 | elements = null;
3618 | disposer.dispose();
3619 | },
3620 | };
3621 | }
3622 |
```