-
Color Palette
+ {/* Navigation Tabs */}
+
+ {[
+ { key: 'colors', label: 'Colors', icon: 'i-ph:palette' },
+ { key: 'typography', label: 'Typography', icon: 'i-ph:text-aa' },
+ { key: 'features', label: 'Features', icon: 'i-ph:magic-wand' },
+ ].map((tab) => (
-
-
- {paletteRoles.map((role) => (
-
-
-
document.getElementById(`color-input-${role.key}`)?.click()}
- />
- handleColorChange(role.key, e.target.value)}
- className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
- tabIndex={-1}
- />
-
-
-
{role.label}
-
{role.description}
-
- {palette[role.key]}
-
-
-
- ))}
-
+ ))}
-
-
- Typography
-
-
- Scroll for more
-
-
-
- {designFonts.map((f) => (
-
- ))}
-
+ {/* Content Area */}
+
+ {activeSection === 'colors' && renderColorSection()}
+ {activeSection === 'typography' && renderTypographySection()}
+ {activeSection === 'features' && renderFeaturesSection()}
-
-
-
Design Features
-
-
- Scroll for more
-
+ {/* Action Buttons */}
+
+
+ {Object.keys(palette).length} colors • {font.length} fonts • {features.length} features
-
- {designFeatures.map((f) => {
- const isSelected = features.includes(f.key);
-
- return (
-
- );
- })}
+
+
+
-
-
-
-
-
+
+
);
};
diff --git a/app/components/workbench/Inspector.tsx b/app/components/workbench/Inspector.tsx
new file mode 100644
index 0000000..198380e
--- /dev/null
+++ b/app/components/workbench/Inspector.tsx
@@ -0,0 +1,126 @@
+import { useEffect, useRef, useState } from 'react';
+
+interface InspectorProps {
+ isActive: boolean;
+ iframeRef: React.RefObject
;
+ onElementSelect: (elementInfo: ElementInfo) => void;
+}
+
+export interface ElementInfo {
+ displayText: string;
+ tagName: string;
+ className: string;
+ id: string;
+ textContent: string;
+ styles: Record; // Changed from CSSStyleDeclaration
+ rect: {
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+ top: number;
+ left: number;
+ };
+}
+
+export const Inspector = ({ isActive, iframeRef, onElementSelect }: InspectorProps) => {
+ const [hoveredElement, setHoveredElement] = useState(null);
+ const overlayRef = useRef(null);
+
+ useEffect(() => {
+ if (!isActive || !iframeRef.current) {
+ return undefined;
+ }
+
+ const iframe = iframeRef.current;
+
+ // Listen for messages from the iframe
+ const handleMessage = (event: MessageEvent) => {
+ if (event.data.type === 'INSPECTOR_HOVER') {
+ const elementInfo = event.data.elementInfo;
+
+ // Adjust coordinates relative to iframe position
+ const iframeRect = iframe.getBoundingClientRect();
+ elementInfo.rect.x += iframeRect.x;
+ elementInfo.rect.y += iframeRect.y;
+ elementInfo.rect.top += iframeRect.y;
+ elementInfo.rect.left += iframeRect.x;
+
+ setHoveredElement(elementInfo);
+ } else if (event.data.type === 'INSPECTOR_CLICK') {
+ const elementInfo = event.data.elementInfo;
+
+ // Adjust coordinates relative to iframe position
+ const iframeRect = iframe.getBoundingClientRect();
+ elementInfo.rect.x += iframeRect.x;
+ elementInfo.rect.y += iframeRect.y;
+ elementInfo.rect.top += iframeRect.y;
+ elementInfo.rect.left += iframeRect.x;
+
+ onElementSelect(elementInfo);
+ } else if (event.data.type === 'INSPECTOR_LEAVE') {
+ setHoveredElement(null);
+ }
+ };
+
+ window.addEventListener('message', handleMessage);
+
+ // Send activation message to iframe
+ const sendActivationMessage = () => {
+ if (iframe.contentWindow) {
+ iframe.contentWindow.postMessage(
+ {
+ type: 'INSPECTOR_ACTIVATE',
+ active: isActive,
+ },
+ '*',
+ );
+ }
+ };
+
+ // Try to send activation message immediately and on load
+ sendActivationMessage();
+ iframe.addEventListener('load', sendActivationMessage);
+
+ return () => {
+ window.removeEventListener('message', handleMessage);
+ iframe.removeEventListener('load', sendActivationMessage);
+
+ // Deactivate inspector in iframe
+ if (iframe.contentWindow) {
+ iframe.contentWindow.postMessage(
+ {
+ type: 'INSPECTOR_ACTIVATE',
+ active: false,
+ },
+ '*',
+ );
+ }
+ };
+ }, [isActive, iframeRef, onElementSelect]);
+
+ // Render overlay for hovered element
+ return (
+ <>
+ {isActive && hoveredElement && (
+
+ {/* Element info tooltip */}
+
+ {hoveredElement.tagName.toLowerCase()}
+ {hoveredElement.id && `#${hoveredElement.id}`}
+ {hoveredElement.className && `.${hoveredElement.className.split(' ')[0]}`}
+
+
+ )}
+ >
+ );
+};
diff --git a/app/components/workbench/InspectorPanel.tsx b/app/components/workbench/InspectorPanel.tsx
new file mode 100644
index 0000000..e2de986
--- /dev/null
+++ b/app/components/workbench/InspectorPanel.tsx
@@ -0,0 +1,146 @@
+import { useState } from 'react';
+
+interface ElementInfo {
+ tagName: string;
+ className: string;
+ id: string;
+ textContent: string;
+ styles: Record; // Changed from CSSStyleDeclaration
+ rect: {
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+ top: number;
+ left: number;
+ };
+}
+
+interface InspectorPanelProps {
+ selectedElement: ElementInfo | null;
+ isVisible: boolean;
+ onClose: () => void;
+}
+
+export const InspectorPanel = ({ selectedElement, isVisible, onClose }: InspectorPanelProps) => {
+ const [activeTab, setActiveTab] = useState<'styles' | 'computed' | 'box'>('styles');
+
+ if (!isVisible || !selectedElement) {
+ return null;
+ }
+
+ const getRelevantStyles = (styles: Record) => {
+ const relevantProps = [
+ 'display',
+ 'position',
+ 'width',
+ 'height',
+ 'margin',
+ 'padding',
+ 'border',
+ 'background',
+ 'color',
+ 'font-size',
+ 'font-family',
+ 'text-align',
+ 'flex-direction',
+ 'justify-content',
+ 'align-items',
+ ];
+
+ return relevantProps.reduce(
+ (acc, prop) => {
+ const value = styles[prop];
+
+ if (value) {
+ acc[prop] = value;
+ }
+
+ return acc;
+ },
+ {} as Record,
+ );
+ };
+
+ return (
+
+ {/* Header */}
+
+
Element Inspector
+
+
+
+ {/* Element Info */}
+
+
+
+ {selectedElement.tagName.toLowerCase()}
+ {selectedElement.id && #{selectedElement.id}}
+ {selectedElement.className && (
+ .{selectedElement.className.split(' ')[0]}
+ )}
+
+ {selectedElement.textContent && (
+
+ "{selectedElement.textContent}"
+
+ )}
+
+
+
+ {/* Tabs */}
+
+ {(['styles', 'computed', 'box'] as const).map((tab) => (
+
+ ))}
+
+
+ {/* Content */}
+
+ {activeTab === 'styles' && (
+
+ {Object.entries(getRelevantStyles(selectedElement.styles)).map(([prop, value]) => (
+
+ {prop}:
+ {value}
+
+ ))}
+
+ )}
+
+ {activeTab === 'box' && (
+
+
+ Width:
+ {Math.round(selectedElement.rect.width)}px
+
+
+ Height:
+ {Math.round(selectedElement.rect.height)}px
+
+
+ Top:
+ {Math.round(selectedElement.rect.top)}px
+
+
+ Left:
+ {Math.round(selectedElement.rect.left)}px
+
+
+ )}
+
+
+ );
+};
diff --git a/app/components/workbench/Preview.tsx b/app/components/workbench/Preview.tsx
index 0d41571..a9917de 100644
--- a/app/components/workbench/Preview.tsx
+++ b/app/components/workbench/Preview.tsx
@@ -6,9 +6,14 @@ import { PortDropdown } from './PortDropdown';
import { ScreenshotSelector } from './ScreenshotSelector';
import { expoUrlAtom } from '~/lib/stores/qrCodeStore';
import { ExpoQrModal } from '~/components/workbench/ExpoQrModal';
+import type { ElementInfo } from './Inspector';
type ResizeSide = 'left' | 'right' | null;
+interface PreviewProps {
+ setSelectedElement?: (element: ElementInfo | null) => void;
+}
+
interface WindowSize {
name: string;
width: number;
@@ -47,11 +52,10 @@ const WINDOW_SIZES: WindowSize[] = [
{ name: '4K Display', width: 3840, height: 2160, icon: 'i-ph:monitor', hasFrame: true, frameType: 'desktop' },
];
-export const Preview = memo(() => {
+export const Preview = memo(({ setSelectedElement }: PreviewProps) => {
const iframeRef = useRef(null);
const containerRef = useRef(null);
const inputRef = useRef(null);
-
const [activePreviewIndex, setActivePreviewIndex] = useState(0);
const [isPortDropdownOpen, setIsPortDropdownOpen] = useState(false);
const [isFullscreen, setIsFullscreen] = useState(false);
@@ -61,11 +65,8 @@ export const Preview = memo(() => {
const [displayPath, setDisplayPath] = useState('/');
const [iframeUrl, setIframeUrl] = useState();
const [isSelectionMode, setIsSelectionMode] = useState(false);
-
- // Toggle between responsive mode and device mode
+ const [isInspectorMode, setIsInspectorMode] = useState(false);
const [isDeviceModeOn, setIsDeviceModeOn] = useState(false);
-
- // Use percentage for width
const [widthPercent, setWidthPercent] = useState(37.5);
const [currentWidth, setCurrentWidth] = useState(0);
@@ -618,6 +619,47 @@ export const Preview = memo(() => {
};
}, [showDeviceFrameInPreview]);
+ useEffect(() => {
+ const handleMessage = (event: MessageEvent) => {
+ if (event.data.type === 'INSPECTOR_READY') {
+ if (iframeRef.current?.contentWindow) {
+ iframeRef.current.contentWindow.postMessage(
+ {
+ type: 'INSPECTOR_ACTIVATE',
+ active: isInspectorMode,
+ },
+ '*',
+ );
+ }
+ } else if (event.data.type === 'INSPECTOR_CLICK') {
+ const element = event.data.elementInfo;
+
+ navigator.clipboard.writeText(element.displayText).then(() => {
+ setSelectedElement?.(element);
+ });
+ }
+ };
+
+ window.addEventListener('message', handleMessage);
+
+ return () => window.removeEventListener('message', handleMessage);
+ }, [isInspectorMode]);
+
+ const toggleInspectorMode = () => {
+ const newInspectorMode = !isInspectorMode;
+ setIsInspectorMode(newInspectorMode);
+
+ if (iframeRef.current?.contentWindow) {
+ iframeRef.current.contentWindow.postMessage(
+ {
+ type: 'INSPECTOR_ACTIVATE',
+ active: newInspectorMode,
+ },
+ '*',
+ );
+ }
+ };
+
return (
{isPortDropdownOpen && (
@@ -697,7 +739,14 @@ export const Preview = memo(() => {
/>
>
)}
-
+
void;
+ setSelectedElement?: (element: ElementInfo | null) => void;
}
const viewTransition = { ease: cubicEasingFn };
@@ -279,7 +281,7 @@ const FileModifiedDropdown = memo(
);
export const Workbench = memo(
- ({ chatStarted, isStreaming, actionRunner, metadata, updateChatMestaData }: WorkspaceProps) => {
+ ({ chatStarted, isStreaming, actionRunner, metadata, updateChatMestaData, setSelectedElement }: WorkspaceProps) => {
renderLogger.trace('Workbench');
const [isSyncing, setIsSyncing] = useState(false);
@@ -487,7 +489,7 @@ export const Workbench = memo(
-
+
diff --git a/app/lib/common/prompts/new-prompt.ts b/app/lib/common/prompts/new-prompt.ts
index ee1bd24..f86c1ea 100644
--- a/app/lib/common/prompts/new-prompt.ts
+++ b/app/lib/common/prompts/new-prompt.ts
@@ -326,14 +326,6 @@ The year is 2025.
When creating designs or UIs for applications, follow these guidelines indefinitely this is non-negotiable:
-
- USER PROVIDED DESIGN SCHEME:
- - ALWAYS use the user provided design scheme when creating designs unless the user specifically requests otherwise.
- FONT: ${JSON.stringify(designScheme?.font)}
- COLOR PALETTE: ${JSON.stringify(designScheme?.palette)}
- FEATURES: ${JSON.stringify(designScheme?.features)}
-
-
CRITICAL:
- Always strive for professional, beautiful, and unique designs
- All designs should be fully featured and worthy of production use
@@ -438,6 +430,14 @@ The year is 2025.
- Use CSS Grid and Flexbox for layouts
- Implement appropriate container queries when needed
- Structure mobile-first designs that progressively enhance for larger screens
+
+
+ USER PROVIDED DESIGN SCHEME:
+ - ALWAYS use the user provided design scheme when creating designs ensuring it complies with the professionalism of design instructions below, unless the user specifically requests otherwise.
+ FONT: ${JSON.stringify(designScheme?.font)}
+ COLOR PALETTE: ${JSON.stringify(designScheme?.palette)}
+ FEATURES: ${JSON.stringify(designScheme?.features)}
+
diff --git a/app/lib/hooks/StickToBottom.tsx b/app/lib/hooks/StickToBottom.tsx
index c07b739..69a0b27 100644
--- a/app/lib/hooks/StickToBottom.tsx
+++ b/app/lib/hooks/StickToBottom.tsx
@@ -133,6 +133,7 @@ function Content({ children, ...props }: StickToBottomContentProps) {
{typeof children === 'function' ? children(context) : children}
+ {/* Blur effect overlay */}
);
}
diff --git a/app/lib/webcontainer/index.ts b/app/lib/webcontainer/index.ts
index f64b59f..0de9401 100644
--- a/app/lib/webcontainer/index.ts
+++ b/app/lib/webcontainer/index.ts
@@ -34,6 +34,10 @@ if (!import.meta.env.SSR) {
const { workbenchStore } = await import('~/lib/stores/workbench');
+ const response = await fetch('/inspector-script.js');
+ const inspectorScript = await response.text();
+ await webcontainer.setPreviewScript(inspectorScript);
+
// Listen for preview errors
webcontainer.on('preview-message', (message) => {
console.log('WebContainer preview message:', message);
diff --git a/app/styles/index.scss b/app/styles/index.scss
index 85f168f..60bff4f 100644
--- a/app/styles/index.scss
+++ b/app/styles/index.scss
@@ -11,6 +11,7 @@ html,
body {
height: 100%;
width: 100%;
+ background-color: var(--bolt-elements-bg-depth-1);
}
:root {
diff --git a/app/types/design-scheme.ts b/app/types/design-scheme.ts
index 0ef1ff6..a37ba97 100644
--- a/app/types/design-scheme.ts
+++ b/app/types/design-scheme.ts
@@ -22,7 +22,6 @@ export const defaultDesignScheme: DesignScheme = {
font: ['sans-serif'],
};
-// Define the semantic color roles for the UI
export const paletteRoles = [
{
key: 'primary',
@@ -84,17 +83,10 @@ export const designFeatures = [
{ key: 'shadow', label: 'Soft Shadow' },
];
-// Add font options for easy reference
export const designFonts = [
{ key: 'sans-serif', label: 'Sans Serif', preview: 'Aa' },
{ key: 'serif', label: 'Serif', preview: 'Aa' },
{ key: 'monospace', label: 'Monospace', preview: 'Aa' },
{ key: 'cursive', label: 'Cursive', preview: 'Aa' },
{ key: 'fantasy', label: 'Fantasy', preview: 'Aa' },
-
- /*
- * Add custom fonts here if needed
- * { key: 'Inter', label: 'Inter', preview: 'Aa' },
- * { key: 'Roboto', label: 'Roboto', preview: 'Aa' },
- */
];
diff --git a/app/utils/markdown.ts b/app/utils/markdown.ts
index accc755..5daf701 100644
--- a/app/utils/markdown.ts
+++ b/app/utils/markdown.ts
@@ -56,6 +56,7 @@ export const allowedHTMLElements = [
'ul',
'var',
'think',
+ 'header',
];
// Add custom rehype plugin
@@ -85,7 +86,7 @@ const rehypeSanitizeOptions: RehypeSanitizeOptions = {
div: [
...(defaultSchema.attributes?.div ?? []),
'data*',
- ['className', '__boltArtifact__', '__boltThought__', '__boltQuickAction'],
+ ['className', '__boltArtifact__', '__boltThought__', '__boltQuickAction', '__boltSelectedElement__'],
// ['className', '__boltThought__']
],
diff --git a/public/inspector-script.js b/public/inspector-script.js
new file mode 100644
index 0000000..4b5a8c2
--- /dev/null
+++ b/public/inspector-script.js
@@ -0,0 +1,292 @@
+(function() {
+ let isInspectorActive = false;
+ let inspectorStyle = null;
+ let currentHighlight = null;
+
+ // Function to get relevant styles
+ function getRelevantStyles(element) {
+ const computedStyles = window.getComputedStyle(element);
+ const relevantProps = [
+ 'display', 'position', 'width', 'height', 'margin', 'padding',
+ 'border', 'background', 'color', 'font-size', 'font-family',
+ 'text-align', 'flex-direction', 'justify-content', 'align-items'
+ ];
+
+ const styles = {};
+ relevantProps.forEach(prop => {
+ const value = computedStyles.getPropertyValue(prop);
+ if (value) styles[prop] = value;
+ });
+
+ return styles;
+ }
+
+ // Function to create a readable element selector
+ function createReadableSelector(element) {
+ let selector = element.tagName.toLowerCase();
+
+ // Add ID if present
+ if (element.id) {
+ selector += `#${element.id}`;
+ }
+
+ // Add classes if present
+ let className = '';
+ if (element.className) {
+ if (typeof element.className === 'string') {
+ className = element.className;
+ } else if (element.className.baseVal !== undefined) {
+ className = element.className.baseVal;
+ } else {
+ className = element.className.toString();
+ }
+
+ if (className.trim()) {
+ const classes = className.trim().split(/\s+/).slice(0, 3); // Limit to first 3 classes
+ selector += `.${classes.join('.')}`;
+ }
+ }
+
+ return selector;
+ }
+
+ // Function to create element display text
+ function createElementDisplayText(element) {
+ const tagName = element.tagName.toLowerCase();
+ let displayText = `<${tagName}`;
+
+ // Add ID attribute
+ if (element.id) {
+ displayText += ` id="${element.id}"`;
+ }
+
+ // Add class attribute (limit to first 3 classes for readability)
+ let className = '';
+ if (element.className) {
+ if (typeof element.className === 'string') {
+ className = element.className;
+ } else if (element.className.baseVal !== undefined) {
+ className = element.className.baseVal;
+ } else {
+ className = element.className.toString();
+ }
+
+ if (className.trim()) {
+ const classes = className.trim().split(/\s+/);
+ const displayClasses = classes.length > 3 ?
+ classes.slice(0, 3).join(' ') + '...' :
+ classes.join(' ');
+ displayText += ` class="${displayClasses}"`;
+ }
+ }
+
+ // Add other important attributes
+ const importantAttrs = ['type', 'name', 'href', 'src', 'alt', 'title'];
+ importantAttrs.forEach(attr => {
+ const value = element.getAttribute(attr);
+ if (value) {
+ const truncatedValue = value.length > 30 ? value.substring(0, 30) + '...' : value;
+ displayText += ` ${attr}="${truncatedValue}"`;
+ }
+ });
+
+ displayText += '>';
+
+ // Add text content preview for certain elements
+ const textElements = ['span', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'button', 'a', 'label'];
+ if (textElements.includes(tagName) && element.textContent) {
+ const textPreview = element.textContent.trim().substring(0, 50);
+ if (textPreview) {
+ displayText += textPreview.length < element.textContent.trim().length ?
+ textPreview + '...' : textPreview;
+ }
+ }
+
+ displayText += `${tagName}>`;
+
+ return displayText;
+ }
+
+ // Function to create element info
+ function createElementInfo(element) {
+ const rect = element.getBoundingClientRect();
+
+ return {
+ tagName: element.tagName,
+ className: getElementClassName(element),
+ id: element.id || '',
+ textContent: element.textContent?.slice(0, 100) || '',
+ styles: getRelevantStyles(element),
+ rect: {
+ x: rect.x,
+ y: rect.y,
+ width: rect.width,
+ height: rect.height,
+ top: rect.top,
+ left: rect.left
+ },
+ // Add new readable formats
+ selector: createReadableSelector(element),
+ displayText: createElementDisplayText(element),
+ elementPath: getElementPath(element)
+ };
+ }
+
+ // Helper function to get element class name consistently
+ function getElementClassName(element) {
+ if (!element.className) return '';
+
+ if (typeof element.className === 'string') {
+ return element.className;
+ } else if (element.className.baseVal !== undefined) {
+ return element.className.baseVal;
+ } else {
+ return element.className.toString();
+ }
+ }
+
+ // Function to get element path (breadcrumb)
+ function getElementPath(element) {
+ const path = [];
+ let current = element;
+
+ while (current && current !== document.body && current !== document.documentElement) {
+ let pathSegment = current.tagName.toLowerCase();
+
+ if (current.id) {
+ pathSegment += `#${current.id}`;
+ } else if (current.className) {
+ const className = getElementClassName(current);
+ if (className.trim()) {
+ const firstClass = className.trim().split(/\s+/)[0];
+ pathSegment += `.${firstClass}`;
+ }
+ }
+
+ path.unshift(pathSegment);
+ current = current.parentElement;
+
+ // Limit path length
+ if (path.length >= 5) break;
+ }
+
+ return path.join(' > ');
+ }
+
+ // Event handlers
+ function handleMouseMove(e) {
+ if (!isInspectorActive) return;
+
+ const target = e.target;
+ if (!target || target === document.body || target === document.documentElement) return;
+
+ // Remove previous highlight
+ if (currentHighlight) {
+ currentHighlight.classList.remove('inspector-highlight');
+ }
+
+ // Add highlight to current element
+ target.classList.add('inspector-highlight');
+ currentHighlight = target;
+
+ const elementInfo = createElementInfo(target);
+
+ // Send message to parent
+ window.parent.postMessage({
+ type: 'INSPECTOR_HOVER',
+ elementInfo: elementInfo
+ }, '*');
+ }
+
+ function handleClick(e) {
+ if (!isInspectorActive) return;
+
+ e.preventDefault();
+ e.stopPropagation();
+
+ const target = e.target;
+ if (!target || target === document.body || target === document.documentElement) return;
+
+ const elementInfo = createElementInfo(target);
+
+ // Send message to parent
+ window.parent.postMessage({
+ type: 'INSPECTOR_CLICK',
+ elementInfo: elementInfo
+ }, '*');
+ }
+
+ function handleMouseLeave() {
+ if (!isInspectorActive) return;
+
+ // Remove highlight
+ if (currentHighlight) {
+ currentHighlight.classList.remove('inspector-highlight');
+ currentHighlight = null;
+ }
+
+ // Send message to parent
+ window.parent.postMessage({
+ type: 'INSPECTOR_LEAVE'
+ }, '*');
+ }
+
+ // Function to activate/deactivate inspector
+ function setInspectorActive(active) {
+ isInspectorActive = active;
+
+ if (active) {
+ // Add inspector styles
+ if (!inspectorStyle) {
+ inspectorStyle = document.createElement('style');
+ inspectorStyle.textContent = `
+ .inspector-active * {
+ cursor: crosshair !important;
+ }
+ .inspector-highlight {
+ outline: 2px solid #3b82f6 !important;
+ outline-offset: -2px !important;
+ background-color: rgba(59, 130, 246, 0.1) !important;
+ }
+ `;
+ document.head.appendChild(inspectorStyle);
+ }
+
+ document.body.classList.add('inspector-active');
+
+ // Add event listeners
+ document.addEventListener('mousemove', handleMouseMove, true);
+ document.addEventListener('click', handleClick, true);
+ document.addEventListener('mouseleave', handleMouseLeave, true);
+ } else {
+ document.body.classList.remove('inspector-active');
+
+ // Remove highlight
+ if (currentHighlight) {
+ currentHighlight.classList.remove('inspector-highlight');
+ currentHighlight = null;
+ }
+
+ // Remove event listeners
+ document.removeEventListener('mousemove', handleMouseMove, true);
+ document.removeEventListener('click', handleClick, true);
+ document.removeEventListener('mouseleave', handleMouseLeave, true);
+
+ // Remove styles
+ if (inspectorStyle) {
+ inspectorStyle.remove();
+ inspectorStyle = null;
+ }
+ }
+ }
+
+ // Listen for messages from parent
+ window.addEventListener('message', function(event) {
+ if (event.data.type === 'INSPECTOR_ACTIVATE') {
+ setInspectorActive(event.data.active);
+ }
+ });
+
+ // Auto-inject if inspector is already active
+ window.parent.postMessage({ type: 'INSPECTOR_READY' }, '*');
+})();
\ No newline at end of file