sonance-brand-mcp 1.3.110 → 1.3.111

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@
2
2
 
3
3
  import React, { useState, useEffect, useCallback, useRef, useMemo } from "react";
4
4
  import { createPortal } from "react-dom";
5
- import { Palette, X, Copy, Check, RotateCcw, ChevronDown, Save, Loader2, AlertCircle, CheckCircle, Sun, Moon, Eye, EyeOff, Zap, Image as ImageIcon, Wand2, Scan, FileCode, Tag, Type, MousePointer, FormInput, Box, Search, Send, Sparkles, RefreshCw, GripHorizontal } from "lucide-react";
5
+ import { Palette, X, Copy, Check, RotateCcw, ChevronDown, Save, Loader2, AlertCircle, CheckCircle, Sun, Moon, Eye, EyeOff, Zap, Image as ImageIcon, Wand2, Scan, FileCode, Tag, Type, MousePointer, FormInput, Box, Search, Send, Sparkles, RefreshCw, GripHorizontal, PanelRightClose, PanelRightOpen, ChevronUp, ChevronDown as ChevronDownIcon } from "lucide-react";
6
6
  import { useTheme } from "next-themes";
7
7
  import { cn } from "../../lib/utils";
8
8
  import {
@@ -49,9 +49,10 @@ import {
49
49
  TextOverride,
50
50
  OriginalTextState,
51
51
  ParentSectionInfo,
52
+ ElementFilters,
53
+ SelectedElementType,
52
54
  } from "./types";
53
55
  import {
54
- tabs,
55
56
  QUICK_ACTIONS,
56
57
  SONANCE_PREVIEW_STYLE_ID,
57
58
  TAILWIND_TO_CSS,
@@ -59,6 +60,8 @@ import {
59
60
  COMPONENT_CONFIG_MAP,
60
61
  getVisibleSections,
61
62
  shouldShowScopeOptions,
63
+ DEFAULT_ELEMENT_FILTERS,
64
+ ELEMENT_TYPE_COLORS,
62
65
  } from "./constants";
63
66
  import {
64
67
  generateComponentCSS,
@@ -180,9 +183,16 @@ function findParentSection(element: Element): ParentSectionInfo | undefined {
180
183
 
181
184
  // ---- Main Component ----
182
185
 
186
+ // Frame layout dimensions
187
+ const BANNER_HEIGHT = 44;
188
+ const SIDEBAR_WIDTH = 260;
189
+ const SIDEBAR_COLLAPSED_WIDTH = 48;
190
+
183
191
  export function SonanceDevTools() {
184
- const [isOpen, setIsOpen] = useState(false);
185
- const [activeTab, setActiveTab] = useState<TabId>("components");
192
+ // Frame is always visible in dev mode - start open by default
193
+ const [isOpen, setIsOpen] = useState(true);
194
+ const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false);
195
+ const [activeTab, setActiveTab] = useState<TabId>("elements");
186
196
  const [config, setConfig] = useState<ThemeConfig>(defaultThemeConfig);
187
197
  const [mounted, setMounted] = useState(false);
188
198
  const [copiedId, setCopiedId] = useState<string | null>(null);
@@ -190,6 +200,11 @@ export function SonanceDevTools() {
190
200
  const [saveMessage, setSaveMessage] = useState<string>("");
191
201
  const [installedComponents, setInstalledComponents] = useState<string[]>([]);
192
202
 
203
+ // Unified element filters - show all element types with color coding
204
+ const [elementFilters, setElementFilters] = useState<ElementFilters>(DEFAULT_ELEMENT_FILTERS);
205
+ // Selected element determines what the sidebar shows
206
+ const [selectedElementType, setSelectedElementType] = useState<SelectedElementType>(null);
207
+
193
208
  // Visual Inspector state - single toggle, behavior depends on active tab
194
209
  const [inspectorEnabled, setInspectorEnabled] = useState(false);
195
210
  const [selectedComponentType, setSelectedComponentType] = useState<string>("all");
@@ -215,12 +230,7 @@ export function SonanceDevTools() {
215
230
  // Track which elements were changed (for highlighting until accept/revert)
216
231
  const [changedElements, setChangedElements] = useState<VisionFocusedElement[]>([]);
217
232
 
218
- // Drag state for movable panel
219
- const DEVTOOLS_POSITION_KEY = "sonance-devtools-pos";
220
- const DEFAULT_POSITION = { x: 0, y: 0 }; // Offset from default bottom-right position
221
- const [dragPosition, setDragPosition] = useState(DEFAULT_POSITION);
222
- const [isDragging, setIsDragging] = useState(false);
223
- const dragOffsetRef = useRef({ x: 0, y: 0 });
233
+ // Refs for panel
224
234
  const panelRef = useRef<HTMLDivElement>(null);
225
235
 
226
236
  // Portal container state - ensures DevTool is always on top of modals
@@ -369,21 +379,74 @@ export function SonanceDevTools() {
369
379
  setMounted(true);
370
380
  }, []);
371
381
 
372
- // Load drag position from localStorage on mount
382
+ // Body layout injection for "squeeze" effect - app content respects dev tools frame
373
383
  useEffect(() => {
374
384
  if (!mounted) return;
375
- try {
376
- const saved = localStorage.getItem(DEVTOOLS_POSITION_KEY);
377
- if (saved) {
378
- const parsed = JSON.parse(saved);
379
- if (typeof parsed.x === "number" && typeof parsed.y === "number") {
380
- setDragPosition(parsed);
385
+
386
+ const root = document.documentElement;
387
+ const body = document.body;
388
+ const styleId = 'sonance-devtools-layout-styles';
389
+
390
+ if (isOpen) {
391
+ const sidebarWidth = isSidebarCollapsed ? SIDEBAR_COLLAPSED_WIDTH : SIDEBAR_WIDTH;
392
+
393
+ // Set CSS variables for components that need to reference the offset
394
+ root.style.setProperty('--devtools-banner-height', `${BANNER_HEIGHT}px`);
395
+ root.style.setProperty('--devtools-sidebar-width', `${sidebarWidth}px`);
396
+
397
+ // Inject styles that adjust fixed elements and body layout
398
+ let styleEl = document.getElementById(styleId) as HTMLStyleElement;
399
+ if (!styleEl) {
400
+ styleEl = document.createElement('style');
401
+ styleEl.id = styleId;
402
+ document.head.appendChild(styleEl);
403
+ }
404
+
405
+ // CSS that creates space for the DevTools
406
+ // Components that read --devtools-banner-height and --devtools-sidebar-width will adjust automatically
407
+ styleEl.textContent = `
408
+ /* Ensure body/html don't overflow */
409
+ html, body {
410
+ margin: 0 !important;
411
+ padding: 0 !important;
412
+ overflow-x: hidden;
381
413
  }
414
+
415
+ /* Push the main content wrapper down */
416
+ body > div:first-child:not([data-sonance-devtools="true"]) {
417
+ padding-top: ${BANNER_HEIGHT}px !important;
418
+ margin-right: ${sidebarWidth}px !important;
419
+ min-height: 100vh;
420
+ box-sizing: border-box;
421
+ }
422
+
423
+ /* Ensure main content areas respect the sidebar width */
424
+ main:not([data-sonance-devtools] *) {
425
+ max-width: calc(100vw - 256px - ${sidebarWidth}px) !important;
426
+ }
427
+ `;
428
+ } else {
429
+ // Reset everything when closed
430
+ root.style.removeProperty('--devtools-banner-height');
431
+ root.style.removeProperty('--devtools-sidebar-width');
432
+
433
+ const styleEl = document.getElementById(styleId);
434
+ if (styleEl) {
435
+ styleEl.remove();
382
436
  }
383
- } catch {
384
- // Ignore parse errors
385
437
  }
386
- }, [mounted, DEVTOOLS_POSITION_KEY]);
438
+
439
+ return () => {
440
+ // Cleanup on unmount
441
+ root.style.removeProperty('--devtools-banner-height');
442
+ root.style.removeProperty('--devtools-sidebar-width');
443
+
444
+ const styleEl = document.getElementById(styleId);
445
+ if (styleEl) {
446
+ styleEl.remove();
447
+ }
448
+ };
449
+ }, [mounted, isOpen, isSidebarCollapsed]);
387
450
 
388
451
  // Restore apply-first session from localStorage on mount
389
452
  // This allows the Accept/Revert UI to survive page refreshes
@@ -420,77 +483,6 @@ export function SonanceDevTools() {
420
483
  }
421
484
  }, [mounted]);
422
485
 
423
- // Drag handlers for movable panel
424
- const headerRef = useRef<HTMLDivElement>(null);
425
-
426
- const handleDragStart = useCallback((e: React.PointerEvent<HTMLDivElement>) => {
427
- // Don't start drag if clicking on a button or interactive element
428
- const target = e.target as HTMLElement;
429
- if (target.closest('button')) return;
430
-
431
- if (!panelRef.current || !headerRef.current) return;
432
-
433
- // Prevent text selection during drag
434
- e.preventDefault();
435
-
436
- // Store the initial mouse position and current panel offset
437
- dragOffsetRef.current = {
438
- x: e.clientX - dragPosition.x,
439
- y: e.clientY - dragPosition.y,
440
- };
441
-
442
- setIsDragging(true);
443
- headerRef.current.setPointerCapture(e.pointerId);
444
- }, [dragPosition]);
445
-
446
- const handleDragMove = useCallback((e: React.PointerEvent<HTMLDivElement>) => {
447
- if (!isDragging || !panelRef.current) return;
448
-
449
- const viewportWidth = window.innerWidth;
450
- const viewportHeight = window.innerHeight;
451
- const panelWidth = panelRef.current.offsetWidth;
452
- const panelHeight = panelRef.current.offsetHeight;
453
-
454
- // Calculate new position directly from mouse movement
455
- const newX = e.clientX - dragOffsetRef.current.x;
456
- const newY = e.clientY - dragOffsetRef.current.y;
457
-
458
- // Clamp to keep panel within viewport (with 24px padding)
459
- const padding = 24;
460
- const maxX = viewportWidth - panelWidth - padding;
461
- const maxY = viewportHeight - panelHeight - padding;
462
- const minX = -(viewportWidth - panelWidth - padding);
463
- const minY = -(viewportHeight - panelHeight - padding);
464
-
465
- setDragPosition({
466
- x: Math.max(minX, Math.min(maxX, newX)),
467
- y: Math.max(minY, Math.min(maxY, newY)),
468
- });
469
- }, [isDragging]);
470
-
471
- const handleDragEnd = useCallback((e: React.PointerEvent<HTMLDivElement>) => {
472
- if (!isDragging || !headerRef.current) return;
473
-
474
- setIsDragging(false);
475
- headerRef.current.releasePointerCapture(e.pointerId);
476
-
477
- // Save position to localStorage
478
- try {
479
- localStorage.setItem(DEVTOOLS_POSITION_KEY, JSON.stringify(dragPosition));
480
- } catch {
481
- // Ignore storage errors
482
- }
483
- }, [isDragging, dragPosition, DEVTOOLS_POSITION_KEY]);
484
-
485
- const handleResetPosition = useCallback(() => {
486
- setDragPosition(DEFAULT_POSITION);
487
- try {
488
- localStorage.removeItem(DEVTOOLS_POSITION_KEY);
489
- } catch {
490
- // Ignore storage errors
491
- }
492
- }, [DEVTOOLS_POSITION_KEY]);
493
-
494
486
  // Inject/update component-specific preview styles whenever overrides change
495
487
  useEffect(() => {
496
488
  if (!mounted) return;
@@ -659,10 +651,10 @@ export function SonanceDevTools() {
659
651
  }
660
652
  }
661
653
 
662
- if (mounted && (activeTab === "logos" || inspectorEnabled) && logoAssets.length === 0) {
654
+ if (mounted && (elementFilters.images || inspectorEnabled) && logoAssets.length === 0) {
663
655
  fetchLogoAssets();
664
656
  }
665
- }, [mounted, activeTab, inspectorEnabled, logoAssets.length]);
657
+ }, [mounted, elementFilters.images, inspectorEnabled, logoAssets.length]);
666
658
 
667
659
  // Helper to extract logo filename from src (handles next/image optimized URLs)
668
660
  const extractLogoName = useCallback((src: string): string | null => {
@@ -759,21 +751,15 @@ export function SonanceDevTools() {
759
751
  // Filter elements for InspectorOverlay based on active tab and selected component type
760
752
  // Now supports both specific component IDs (e.g., "button-primary") and categories (e.g., "buttons")
761
753
  const filteredOverlayElements = useMemo(() => {
762
- // Filter based on active tab first - only show relevant element types
763
- let filtered = taggedElements;
764
-
765
- if (activeTab === "logos") {
766
- // On logos tab, only show logos
767
- filtered = taggedElements.filter(el => el.type === "logo");
768
- } else if (activeTab === "text") {
769
- // On text tab, only show text elements
770
- filtered = taggedElements.filter(el => el.type === "text");
771
- } else if (activeTab === "components") {
772
- // On components tab, only show components
773
- filtered = taggedElements.filter(el => el.type === "component");
774
- }
754
+ // Filter based on element type filters (unified color-coded view)
755
+ let filtered = taggedElements.filter(el => {
756
+ if (el.type === "component" && !elementFilters.components) return false;
757
+ if (el.type === "logo" && !elementFilters.images) return false;
758
+ if (el.type === "text" && !elementFilters.text) return false;
759
+ return true;
760
+ });
775
761
 
776
- // When inspector is enabled, show elements for the current tab
762
+ // When inspector is enabled, show all filtered elements
777
763
  if (inspectorEnabled) {
778
764
  return filtered;
779
765
  }
@@ -781,19 +767,19 @@ export function SonanceDevTools() {
781
767
  // When viewing inspector mode for a selected component, show ALL elements
782
768
  // (the InspectorOverlay will handle highlighting/dimming based on type match)
783
769
  if (viewMode === "inspector" && selectedComponentType !== "all") {
784
- return taggedElements;
770
+ return filtered;
785
771
  }
786
772
 
787
773
  if (selectedComponentType === "all") {
788
- return taggedElements;
774
+ return filtered;
789
775
  }
790
776
 
791
777
  // Check if this is a specific component ID (exists in componentSnippets)
792
778
  const isSpecificComponent = componentSnippets.some(s => s.id === selectedComponentType);
793
779
 
794
780
  // Filter components based on selected type
795
- return taggedElements.filter((el) => {
796
- // Always include logos and text when their respective inspectors are enabled
781
+ return filtered.filter((el) => {
782
+ // Always include logos and text (they're already filtered by elementFilters)
797
783
  if (el.type === "logo" || el.type === "text") return true;
798
784
 
799
785
  // For component type filtering
@@ -832,7 +818,7 @@ export function SonanceDevTools() {
832
818
 
833
819
  return true;
834
820
  });
835
- }, [taggedElements, selectedComponentType, inspectorEnabled, viewMode]);
821
+ }, [taggedElements, selectedComponentType, inspectorEnabled, viewMode, elementFilters]);
836
822
 
837
823
  // Simple string hash function for variant detection
838
824
  const generateHash = (str: string): string => {
@@ -848,9 +834,8 @@ export function SonanceDevTools() {
848
834
 
849
835
  // Visual Inspector: Scan for tagged elements, logos, and text, update positions
850
836
  useEffect(() => {
851
- // Scan if any inspector is enabled OR if we are on the components tab (to populate the list)
852
- // Scan when inspector is enabled - scans based on active tab
853
- const shouldScan = inspectorEnabled || activeTab === "components" || activeTab === "logos" || activeTab === "text";
837
+ // Scan when inspector is enabled or on elements tab - now unified with filters
838
+ const shouldScan = inspectorEnabled || activeTab === "elements";
854
839
 
855
840
  // console.log("Scan effect running. shouldScan:", shouldScan, "activeTab:", activeTab);
856
841
 
@@ -906,8 +891,8 @@ export function SonanceDevTools() {
906
891
  return true;
907
892
  };
908
893
 
909
- // Scan for tagged components
910
- if (inspectorEnabled || activeTab === "components") {
894
+ // Scan for tagged components (always scan when filters allow)
895
+ if ((inspectorEnabled || activeTab === "elements") && elementFilters.components) {
911
896
  // 1. Scan for explicitly tagged components
912
897
  const taggedComponents = document.querySelectorAll("[data-sonance-name]");
913
898
  taggedComponents.forEach((el) => {
@@ -997,8 +982,8 @@ export function SonanceDevTools() {
997
982
  });
998
983
  }
999
984
 
1000
- // Scan for logo images when on logos tab
1001
- if (activeTab === "logos") {
985
+ // Scan for logo/image elements (always scan when filters allow)
986
+ if (elementFilters.images) {
1002
987
  const images = document.querySelectorAll("img");
1003
988
  images.forEach((img) => {
1004
989
  // Skip elements outside the active layer (DevTools, behind modals)
@@ -1047,9 +1032,8 @@ export function SonanceDevTools() {
1047
1032
  });
1048
1033
  }
1049
1034
 
1050
- // Scan for text elements
1051
- // Scan for text when on text tab and inspector is enabled
1052
- if (activeTab === "text" && inspectorEnabled) {
1035
+ // Scan for text elements (always scan when filters allow and inspector enabled)
1036
+ if (elementFilters.text && inspectorEnabled) {
1053
1037
  // Only select meaningful text containers - exclude spans as they're usually nested
1054
1038
  const textSelectors = "h1, h2, h3, h4, h5, h6, p, a, label, blockquote, figcaption, li";
1055
1039
  const textElements = document.querySelectorAll(textSelectors);
@@ -1148,7 +1132,7 @@ export function SonanceDevTools() {
1148
1132
  inspectorRef.current = null;
1149
1133
  }
1150
1134
  };
1151
- }, [inspectorEnabled, mounted, extractLogoName, originalLogoStates, activeTab]);
1135
+ }, [inspectorEnabled, mounted, extractLogoName, originalLogoStates, activeTab, elementFilters]);
1152
1136
 
1153
1137
  // Toggle Visual Inspector
1154
1138
  const toggleInspector = useCallback((force?: boolean | unknown) => {
@@ -1156,20 +1140,27 @@ export function SonanceDevTools() {
1156
1140
  }, []);
1157
1141
 
1158
1142
  // Toggle Vision Mode - AI-powered page-wide editing
1143
+ // Inspector functionality is now integrated into Vision Mode
1144
+ // Vision mode and Analysis tab are mutually exclusive
1159
1145
  const toggleVisionMode = useCallback(() => {
1160
1146
  setVisionModeActive((prev) => {
1161
1147
  const newValue = !prev;
1162
1148
  if (newValue) {
1163
- // When enabling vision mode, also enable inspector for element selection
1149
+ // When enabling vision mode, auto-enable inspector highlighting
1164
1150
  setInspectorEnabled(true);
1151
+ // If on Analysis tab, switch to Elements tab (mutually exclusive)
1152
+ if (activeTab === "analysis") {
1153
+ setActiveTab("elements");
1154
+ }
1165
1155
  } else {
1166
- // When disabling, clear all vision state
1156
+ // When disabling, clear all vision state AND disable inspector
1157
+ setInspectorEnabled(false);
1167
1158
  setVisionFocusedElements([]);
1168
1159
  setVisionScreenshot(null);
1169
1160
  }
1170
1161
  return newValue;
1171
1162
  });
1172
- }, []);
1163
+ }, [activeTab]);
1173
1164
 
1174
1165
  // Handle vision mode element click - toggle focus on/off
1175
1166
  const handleVisionElementClick = useCallback((element: DetectedElement) => {
@@ -1219,13 +1210,11 @@ export function SonanceDevTools() {
1219
1210
  );
1220
1211
 
1221
1212
  if (exists) {
1222
- // Remove (toggle off)
1223
- return prev.filter(
1224
- (el) => !(el.name === element.name && el.variantId === element.variantId)
1225
- );
1213
+ // Click on same element - deselect it
1214
+ return [];
1226
1215
  } else {
1227
- // Add (toggle on)
1228
- return [...prev, focusedElement];
1216
+ // Click on different element - select only this one (single selection)
1217
+ return [focusedElement];
1229
1218
  }
1230
1219
  });
1231
1220
  }, [visionModeActive]);
@@ -1454,14 +1443,18 @@ export function SonanceDevTools() {
1454
1443
  }, [applyFirstSession]);
1455
1444
 
1456
1445
  // Handle tab change - clear selections when switching tabs
1446
+ // Now simplified: just "analysis" and "elements" modes
1457
1447
  const handleTabChange = useCallback((newTab: TabId) => {
1458
1448
  if (newTab !== activeTab) {
1459
- // Clear component selections when leaving components tab
1460
- if (activeTab === "components") {
1449
+ // Clear selections when leaving elements tab
1450
+ if (activeTab === "elements") {
1461
1451
  setSelectedComponentType("all");
1462
1452
  setSelectedComponentId(null);
1463
1453
  setSelectedVariantId(null);
1464
- // Also clear vision mode when leaving components tab
1454
+ setSelectedLogoId(null);
1455
+ setSelectedTextId(null);
1456
+ setSelectedElementType(null);
1457
+ // Clear vision mode when leaving elements
1465
1458
  if (visionModeActive) {
1466
1459
  clearVisionMode();
1467
1460
  }
@@ -1470,13 +1463,13 @@ export function SonanceDevTools() {
1470
1463
  handleApplyFirstRevert();
1471
1464
  }
1472
1465
  }
1473
- // Clear logo selection when leaving logos tab
1474
- if (activeTab === "logos") {
1475
- setSelectedLogoId(null);
1476
- }
1477
- // Clear text selection when leaving text tab
1478
- if (activeTab === "text") {
1479
- setSelectedTextId(null);
1466
+ // Analysis and Vision mode are mutually exclusive
1467
+ // When switching to Analysis, disable Vision mode
1468
+ if (newTab === "analysis" && visionModeActive) {
1469
+ setVisionModeActive(false);
1470
+ setInspectorEnabled(false);
1471
+ setVisionFocusedElements([]);
1472
+ setVisionScreenshot(null);
1480
1473
  }
1481
1474
  setActiveTab(newTab);
1482
1475
  }
@@ -2543,118 +2536,98 @@ export function SonanceDevTools() {
2543
2536
 
2544
2537
  if (!mounted) return null;
2545
2538
 
2546
- const trigger = (
2547
- <button
2548
- onClick={() => setIsOpen(true)}
2549
- className={cn(
2550
- "fixed bottom-6 right-6 z-[2147483646] pointer-events-auto",
2551
- "flex h-14 w-14 items-center justify-center",
2552
- "rounded-full bg-[#333F48] text-white shadow-lg",
2553
- "hover:scale-105 hover:shadow-xl",
2554
- "transition-all duration-200",
2555
- "focus:outline-none focus:ring-2 focus:ring-[#00A3E1] focus:ring-offset-2",
2556
- isOpen && "hidden"
2557
- )}
2558
- aria-label="Open Sonance DevTools"
2559
- >
2560
- <Palette className="h-6 w-6" />
2561
- </button>
2562
- );
2539
+ // Glassmorphism base classes
2540
+ const glassBase = "backdrop-blur-xl bg-black/75 border-white/10";
2541
+
2542
+ const sidebarWidth = isSidebarCollapsed ? SIDEBAR_COLLAPSED_WIDTH : SIDEBAR_WIDTH;
2563
2543
 
2564
- const panel = isOpen && (
2544
+ // ======== TOP BANNER ========
2545
+ const topBanner = isOpen && (
2565
2546
  <div
2566
- ref={panelRef}
2567
- data-sonance-devtools="true"
2547
+ data-sonance-devtools="banner"
2568
2548
  className={cn(
2569
- "fixed bottom-6 right-6 z-[2147483647] pointer-events-auto",
2570
- "w-[360px] max-h-[80vh]",
2571
- "bg-white rounded-lg shadow-2xl border border-gray-200",
2572
- "flex flex-col overflow-hidden",
2573
- "font-['Montserrat',sans-serif]",
2574
- isDragging && "select-none"
2549
+ "fixed top-0 left-0 right-0 z-[2147483647] pointer-events-auto",
2550
+ "flex items-center justify-between px-4",
2551
+ "border-b font-['Montserrat',sans-serif]",
2552
+ glassBase,
2553
+ "border-b-white/10"
2575
2554
  )}
2576
2555
  style={{
2577
- colorScheme: "light",
2578
- transform: `translate(${dragPosition.x}px, ${dragPosition.y}px)`,
2556
+ height: `${BANNER_HEIGHT}px`,
2557
+ colorScheme: "dark",
2579
2558
  }}
2580
- // Event isolation - prevent clicks from closing popups in the main app
2581
- // onPointerDown fires before onMouseDown in modern browsers - must stop both
2582
- onPointerDown={(e) => e.stopPropagation()}
2583
- onTouchStart={(e) => e.stopPropagation()}
2584
- onMouseDown={(e) => e.stopPropagation()}
2585
- onClick={(e) => e.stopPropagation()}
2586
- onFocus={(e) => e.stopPropagation()}
2587
- onBlur={(e) => e.stopPropagation()}
2588
2559
  >
2589
- {/* Header - Draggable */}
2590
- <div
2591
- ref={headerRef}
2592
- className={cn(
2593
- "flex items-center justify-between px-3 py-2 border-b border-gray-200 bg-[#333F48]",
2594
- "cursor-move touch-none"
2595
- )}
2596
- onPointerDown={handleDragStart}
2597
- onPointerMove={handleDragMove}
2598
- onPointerUp={handleDragEnd}
2599
- onPointerCancel={handleDragEnd}
2600
- onDoubleClick={handleResetPosition}
2601
- title="Drag to move • Double-click to reset position"
2602
- >
2603
- <div className="flex items-center gap-1.5">
2604
- <GripHorizontal className="h-3.5 w-3.5 text-white/50" />
2605
- <Palette className="h-4 w-4 text-[#00A3E1]" />
2606
- <span id="span-sonance-devtools" className="text-xs font-semibold text-white whitespace-nowrap">
2560
+ {/* Left: Branding */}
2561
+ <div className="flex items-center gap-3">
2562
+ <div className="flex items-center gap-2">
2563
+ <div className="flex items-center justify-center w-8 h-8 rounded-lg bg-gradient-to-br from-[#00D3C8] to-[#00A3E1]">
2564
+ <Palette className="h-4 w-4 text-white" />
2565
+ </div>
2566
+ <div className="flex flex-col">
2567
+ <span className="text-sm font-semibold text-white tracking-tight">
2607
2568
  Sonance DevTools
2608
2569
  </span>
2570
+ <span className="text-[10px] text-white/50 uppercase tracking-widest">
2571
+ Design System
2572
+ </span>
2573
+ </div>
2609
2574
  </div>
2610
- <div className="flex items-center gap-1">
2611
- {/* Analysis Tab Toggle */}
2575
+ </div>
2576
+
2577
+ {/* Center: Mode Toggles */}
2578
+ <div className="flex items-center gap-1 bg-white/5 rounded-full px-2 py-1">
2579
+ {/* Analysis Toggle */}
2612
2580
  <button
2613
2581
  onClick={() => handleTabChange(activeTab === "analysis" ? "components" : "analysis")}
2614
2582
  className={cn(
2615
- "p-1.5 rounded transition-colors",
2583
+ "flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium transition-all",
2616
2584
  activeTab === "analysis"
2617
- ? "bg-[#00A3E1] text-white"
2618
- : "hover:bg-white/10 text-white/80"
2585
+ ? "bg-[#00D3C8] text-white shadow-lg shadow-[#00D3C8]/25"
2586
+ : "text-white/70 hover:text-white hover:bg-white/10"
2619
2587
  )}
2620
- aria-label={activeTab === "analysis" ? "Close analysis" : "Open analysis"}
2621
- title={activeTab === "analysis" ? "Close analysis" : "Open analysis"}
2588
+ title="Page Analysis"
2622
2589
  >
2623
- <Scan className="h-4 w-4" />
2590
+ <Scan className="h-3.5 w-3.5" />
2591
+ <span className="hidden sm:inline">Analysis</span>
2624
2592
  </button>
2625
- {/* Inspector Toggle */}
2593
+ {/* Vision Mode Toggle */}
2626
2594
  <button
2627
- onClick={toggleInspector}
2595
+ onClick={toggleVisionMode}
2628
2596
  className={cn(
2629
- "p-1.5 rounded transition-colors",
2630
- inspectorEnabled
2631
- ? "bg-[#00A3E1] text-white"
2632
- : "hover:bg-white/10 text-white/80"
2633
- )}
2634
- aria-label={inspectorEnabled ? "Disable inspector" : "Enable inspector"}
2635
- title={inspectorEnabled ? "Disable inspector" : "Enable inspector"}
2636
- >
2637
- <MousePointer className={cn("h-4 w-4", inspectorEnabled && "animate-pulse")} />
2597
+ "flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium transition-all",
2598
+ visionModeActive
2599
+ ? "bg-[#8B5CF6] text-white shadow-lg shadow-[#8B5CF6]/25"
2600
+ : "text-white/70 hover:text-white hover:bg-white/10"
2601
+ )}
2602
+ title="AI Vision Mode"
2603
+ >
2604
+ <Eye className={cn("h-3.5 w-3.5", visionModeActive && "animate-pulse")} />
2605
+ <span className="hidden sm:inline">Vision</span>
2638
2606
  </button>
2639
- {/* Vision Mode Toggle */}
2607
+ {/* Element Highlight Toggle - only visible when Vision Mode is active */}
2608
+ {visionModeActive && (
2640
2609
  <button
2641
- onClick={toggleVisionMode}
2610
+ onClick={toggleInspector}
2642
2611
  className={cn(
2643
- "p-1.5 rounded transition-colors",
2644
- visionModeActive
2645
- ? "bg-[#8B5CF6] text-white"
2646
- : "hover:bg-white/10 text-white/80"
2612
+ "flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium transition-all",
2613
+ inspectorEnabled
2614
+ ? "bg-[#00A3E1] text-white shadow-lg shadow-[#00A3E1]/25"
2615
+ : "text-white/70 hover:text-white hover:bg-white/10"
2647
2616
  )}
2648
- aria-label={visionModeActive ? "Exit vision mode" : "Enter vision mode"}
2649
- title="Vision Mode: AI-powered page editing with screenshot analysis"
2617
+ title={inspectorEnabled ? "Disable element highlighting" : "Enable element highlighting"}
2650
2618
  >
2651
- <Eye className={cn("h-4 w-4", visionModeActive && "animate-pulse")} />
2619
+ <MousePointer className={cn("h-3.5 w-3.5", inspectorEnabled && "animate-pulse")} />
2620
+ <span className="hidden sm:inline">{inspectorEnabled ? "Highlighting" : "Highlight"}</span>
2652
2621
  </button>
2653
- {/* Theme Mode Toggle */}
2622
+ )}
2623
+ </div>
2624
+
2625
+ {/* Right: Actions */}
2626
+ <div className="flex items-center gap-2">
2627
+ {/* Theme Toggle */}
2654
2628
  <button
2655
2629
  onClick={toggleThemeMode}
2656
- className="p-1.5 rounded hover:bg-white/10 transition-colors"
2657
- aria-label={resolvedTheme === "dark" ? "Switch to light mode" : "Switch to dark mode"}
2630
+ className="flex items-center justify-center w-8 h-8 rounded-lg bg-white/5 hover:bg-white/10 transition-colors"
2658
2631
  title={resolvedTheme === "dark" ? "Switch to light mode" : "Switch to dark mode"}
2659
2632
  >
2660
2633
  {resolvedTheme === "dark" ? (
@@ -2666,182 +2639,285 @@ export function SonanceDevTools() {
2666
2639
  {/* Close Button */}
2667
2640
  <button
2668
2641
  onClick={handleClose}
2669
- className="p-1.5 rounded hover:bg-white/10 transition-colors"
2670
- aria-label="Close"
2642
+ className="flex items-center justify-center w-8 h-8 rounded-lg bg-white/5 hover:bg-red-500/20 hover:text-red-400 transition-colors text-white/70"
2643
+ title="Close DevTools"
2671
2644
  >
2672
- <X className="h-4 w-4 text-white" />
2645
+ <X className="h-4 w-4" />
2673
2646
  </button>
2674
2647
  </div>
2675
2648
  </div>
2649
+ );
2676
2650
 
2677
- {/* Icon Navigation Bar */}
2678
- <div className="flex items-center justify-evenly border-b border-gray-200 bg-gray-50 px-2 py-1">
2679
- {tabs.map((tab) => {
2680
- const IconComponent = tab.icon;
2681
- const isActive = activeTab === tab.id;
2682
- return (
2651
+ // ======== RIGHT SIDEBAR ========
2652
+ const sidebar = isOpen && (
2653
+ <div
2654
+ ref={panelRef}
2655
+ data-sonance-devtools="sidebar"
2656
+ className={cn(
2657
+ "fixed top-0 right-0 z-[2147483646] pointer-events-auto",
2658
+ "flex flex-col font-['Montserrat',sans-serif]",
2659
+ "border-l",
2660
+ glassBase,
2661
+ "border-l-white/10",
2662
+ "transition-all duration-200 ease-out"
2663
+ )}
2664
+ style={{
2665
+ width: `${sidebarWidth}px`,
2666
+ height: "100vh",
2667
+ paddingTop: `${BANNER_HEIGHT}px`,
2668
+ colorScheme: "dark",
2669
+ }}
2670
+ onPointerDown={(e) => e.stopPropagation()}
2671
+ onTouchStart={(e) => e.stopPropagation()}
2672
+ onMouseDown={(e) => e.stopPropagation()}
2673
+ onClick={(e) => e.stopPropagation()}
2674
+ >
2675
+ {/* Collapse/Expand Toggle */}
2683
2676
  <button
2684
- key={tab.id}
2685
- onClick={() => handleTabChange(tab.id)}
2677
+ onClick={() => setIsSidebarCollapsed(!isSidebarCollapsed)}
2686
2678
  className={cn(
2687
- "p-2 rounded-md transition-all",
2688
- isActive
2689
- ? "bg-[#333F48] text-white"
2690
- : "text-gray-500 hover:text-gray-700 hover:bg-gray-100"
2691
- )}
2692
- aria-label={tab.label}
2693
- title={tab.label}
2694
- >
2695
- <IconComponent className="h-4 w-4" />
2696
- </button>
2697
- );
2698
- })}
2699
- </div>
2700
-
2701
- {/* Content */}
2702
- <div
2703
- className="flex-1 overflow-y-auto p-4 text-[#333F48]"
2704
- style={{
2705
- overscrollBehavior: "contain", // Prevent scroll chaining to parent (modal)
2706
- WebkitOverflowScrolling: "touch", // Smooth scrolling on iOS
2707
- }}
2708
- onWheel={(e) => e.stopPropagation()} // Prevent modal from intercepting wheel events
2709
- onTouchMove={(e) => e.stopPropagation()} // Prevent modal from intercepting touch scroll
2710
- >
2711
- {/* Analysis Tab */}
2712
- {activeTab === "analysis" && (
2713
- <AnalysisPanel
2714
- analysisStatus={analysisStatus}
2715
- analysisResult={analysisResult}
2716
- analysisError={analysisError}
2717
- bulkTagStatus={bulkTagStatus}
2718
- bulkTagMessage={bulkTagMessage}
2719
- onRunAnalysis={handleRunAnalysis}
2720
- onBulkTag={handleBulkTagAll}
2721
- />
2679
+ "absolute -left-3 top-1/2 -translate-y-1/2 z-10",
2680
+ "flex items-center justify-center w-6 h-12 rounded-l-lg",
2681
+ "bg-black/80 border border-white/10 border-r-0",
2682
+ "text-white/60 hover:text-white hover:bg-black/90",
2683
+ "transition-all"
2722
2684
  )}
2723
-
2724
- {/* Components Tab */}
2725
- {activeTab === "components" && (
2726
- <ComponentsPanel
2727
- copiedId={copiedId}
2728
- onCopy={handleCopy}
2729
- installedComponents={installedComponents}
2730
- inspectorEnabled={inspectorEnabled}
2731
- onToggleInspector={toggleInspector}
2732
- config={config}
2733
- updateConfig={updateConfig}
2734
- onReset={handleReset}
2735
- taggedElements={taggedElements}
2736
- selectedComponentType={selectedComponentType}
2737
- onSelectComponentType={handleSelectComponentType}
2738
- componentScope={componentScope}
2739
- onScopeChange={setComponentScope}
2740
- selectedComponentId={selectedComponentId}
2741
- onSelectComponent={setSelectedComponentId}
2742
- selectedVariantId={selectedVariantId}
2743
- onSelectVariant={setSelectedVariantId}
2744
- analysisResult={analysisResult}
2745
- componentOverrides={componentOverrides}
2746
- onUpdateComponentOverride={updateComponentOverride}
2747
- onResetComponentOverrides={resetComponentOverrides}
2748
- viewMode={viewMode}
2749
- onViewModeChange={setViewMode}
2750
- isPreviewActive={isPreviewActive}
2751
- onPreviewActiveChange={setIsPreviewActive}
2752
- visionMode={visionModeActive}
2753
- visionFocusedElements={visionFocusedElements}
2754
- onVisionEditComplete={handleVisionEditComplete}
2755
- visionPendingEdit={visionPendingEdit}
2756
- onSaveVisionEdit={handleSaveVisionEdit}
2757
- onClearVisionPendingEdit={handleClearVisionPendingEdit}
2758
- // Apply-First Mode props
2759
- applyFirstSession={applyFirstSession}
2760
- applyFirstStatus={applyFirstStatus}
2761
- onApplyFirstComplete={handleApplyFirstComplete}
2762
- onApplyFirstAccept={handleApplyFirstAccept}
2763
- onApplyFirstRevert={handleApplyFirstRevert}
2764
- onApplyFirstForceClear={handleApplyFirstForceClear}
2765
- />
2685
+ title={isSidebarCollapsed ? "Expand sidebar" : "Collapse sidebar"}
2686
+ >
2687
+ {isSidebarCollapsed ? (
2688
+ <PanelRightClose className="h-3.5 w-3.5" />
2689
+ ) : (
2690
+ <PanelRightOpen className="h-3.5 w-3.5" />
2766
2691
  )}
2692
+ </button>
2767
2693
 
2768
- {/* Logos Tab */}
2769
- {activeTab === "logos" && (
2770
- <LogosPanel
2771
- logoAssets={logoAssets}
2772
- logoAssetsByBrand={logoAssetsByBrand}
2773
- selectedLogoId={selectedLogoId}
2774
- globalLogoConfig={globalLogoConfig}
2775
- individualLogoConfigs={individualLogoConfigs}
2776
- originalLogoStates={originalLogoStates}
2777
- taggedElements={taggedElements}
2778
- onGlobalConfigChange={handleGlobalLogoConfigChange}
2779
- onIndividualConfigChange={handleIndividualLogoConfigChange}
2780
- onSelectLogo={handleSelectLogo}
2781
- onResetAll={handleResetAllLogos}
2782
- onResetLogo={handleResetLogo}
2783
- onSaveChanges={handleSaveLogoChanges}
2784
- saveStatus={logoSaveStatus}
2785
- saveMessage={logoSaveMessage}
2786
- findComplementaryLogo={findComplementaryLogo}
2787
- currentTheme={resolvedTheme || theme || "light"}
2788
- onAutoFixId={handleAutoFixId}
2789
- autoFixStatus={autoFixStatus}
2790
- autoFixMessage={autoFixMessage}
2791
- />
2792
- )}
2793
-
2794
- {/* Text Tab */}
2795
- {activeTab === "text" && (
2796
- <TextPanel
2797
- inspectorEnabled={inspectorEnabled}
2798
- taggedElements={taggedElements}
2799
- selectedTextId={selectedTextId}
2800
- onSelectText={handleSelectText}
2801
- textOverrides={textOverrides}
2802
- onTextOverrideChange={handleTextOverrideChange}
2803
- hasChanges={hasTextChanges}
2804
- onSaveChanges={handleSaveTextChanges}
2805
- onRevertAll={handleRevertAllTextChanges}
2806
- saveStatus={textSaveStatus}
2807
- currentTheme={currentResolvedTheme === "dark" ? "dark" : "light"}
2808
- />
2809
- )}
2694
+ {/* Element Type Filter Toggles - Color coded */}
2695
+ {!isSidebarCollapsed && activeTab === "elements" && (
2696
+ <div className="flex items-center gap-1 px-2 py-2 border-b border-white/10 bg-white/5">
2697
+ {/* Components Filter */}
2698
+ <button
2699
+ onClick={() => setElementFilters(prev => ({ ...prev, components: !prev.components }))}
2700
+ className={cn(
2701
+ "flex items-center gap-1 px-2 py-1.5 rounded text-[10px] font-medium transition-all",
2702
+ elementFilters.components
2703
+ ? "bg-purple-500/20 text-purple-300 border border-purple-400/30"
2704
+ : "text-white/40 hover:text-white/60 border border-transparent"
2705
+ )}
2706
+ title="Toggle Components"
2707
+ >
2708
+ <Box className="h-3 w-3" />
2709
+ <span>Components</span>
2710
+ </button>
2711
+ {/* Images Filter */}
2712
+ <button
2713
+ onClick={() => setElementFilters(prev => ({ ...prev, images: !prev.images }))}
2714
+ className={cn(
2715
+ "flex items-center gap-1 px-2 py-1.5 rounded text-[10px] font-medium transition-all",
2716
+ elementFilters.images
2717
+ ? "bg-blue-500/20 text-blue-300 border border-blue-400/30"
2718
+ : "text-white/40 hover:text-white/60 border border-transparent"
2719
+ )}
2720
+ title="Toggle Images"
2721
+ >
2722
+ <ImageIcon className="h-3 w-3" />
2723
+ <span>Images</span>
2724
+ </button>
2725
+ {/* Text Filter */}
2726
+ <button
2727
+ onClick={() => setElementFilters(prev => ({ ...prev, text: !prev.text }))}
2728
+ className={cn(
2729
+ "flex items-center gap-1 px-2 py-1.5 rounded text-[10px] font-medium transition-all",
2730
+ elementFilters.text
2731
+ ? "bg-amber-500/20 text-amber-300 border border-amber-400/30"
2732
+ : "text-white/40 hover:text-white/60 border border-transparent"
2733
+ )}
2734
+ title="Toggle Text"
2735
+ >
2736
+ <Type className="h-3 w-3" />
2737
+ <span>Text</span>
2738
+ </button>
2810
2739
  </div>
2740
+ )}
2741
+ {/* Collapsed sidebar - just show active filters as icons */}
2742
+ {isSidebarCollapsed && activeTab === "elements" && (
2743
+ <div className="flex flex-col items-center gap-0.5 py-1.5 border-b border-white/10 bg-white/5">
2744
+ <button
2745
+ onClick={() => setElementFilters(prev => ({ ...prev, components: !prev.components }))}
2746
+ className={cn(
2747
+ "p-1.5 rounded transition-all",
2748
+ elementFilters.components ? "text-purple-400" : "text-white/30"
2749
+ )}
2750
+ title="Components"
2751
+ >
2752
+ <Box className="h-3.5 w-3.5" />
2753
+ </button>
2754
+ <button
2755
+ onClick={() => setElementFilters(prev => ({ ...prev, images: !prev.images }))}
2756
+ className={cn(
2757
+ "p-1.5 rounded transition-all",
2758
+ elementFilters.images ? "text-blue-400" : "text-white/30"
2759
+ )}
2760
+ title="Images"
2761
+ >
2762
+ <ImageIcon className="h-3.5 w-3.5" />
2763
+ </button>
2764
+ <button
2765
+ onClick={() => setElementFilters(prev => ({ ...prev, text: !prev.text }))}
2766
+ className={cn(
2767
+ "p-1.5 rounded transition-all",
2768
+ elementFilters.text ? "text-amber-400" : "text-white/30"
2769
+ )}
2770
+ title="Text"
2771
+ >
2772
+ <Type className="h-3.5 w-3.5" />
2773
+ </button>
2774
+ </div>
2775
+ )}
2776
+
2777
+ {/* Panel Content - Light themed for contrast with dark frame */}
2778
+ {!isSidebarCollapsed && (
2779
+ <div
2780
+ className={cn(
2781
+ "flex-1 m-1.5 rounded-md bg-white/95 backdrop-blur-sm shadow-inner",
2782
+ // Elements tab needs flex layout for pinned chat
2783
+ activeTab === "elements" ? "flex flex-col overflow-hidden" : "overflow-y-auto"
2784
+ )}
2785
+ style={{
2786
+ overscrollBehavior: "contain",
2787
+ WebkitOverflowScrolling: "touch",
2788
+ colorScheme: "light",
2789
+ }}
2790
+ onWheel={(e) => e.stopPropagation()}
2791
+ onTouchMove={(e) => e.stopPropagation()}
2792
+ >
2793
+ {/* Analysis Tab - with padding wrapper */}
2794
+ {activeTab === "analysis" && (
2795
+ <div className="p-3 text-[#333F48]">
2796
+ <AnalysisPanel
2797
+ analysisStatus={analysisStatus}
2798
+ analysisResult={analysisResult}
2799
+ analysisError={analysisError}
2800
+ bulkTagStatus={bulkTagStatus}
2801
+ bulkTagMessage={bulkTagMessage}
2802
+ onRunAnalysis={handleRunAnalysis}
2803
+ onBulkTag={handleBulkTagAll}
2804
+ />
2805
+ </div>
2806
+ )}
2807
+
2808
+ {/* Unified Elements Tab - full height flex layout */}
2809
+ {activeTab === "elements" && (
2810
+ <div className="flex-1 flex flex-col min-h-0 text-[#333F48]">
2811
+ {/* Dynamic content based on selected element type or default to ComponentsPanel */}
2812
+ <ComponentsPanel
2813
+ copiedId={copiedId}
2814
+ onCopy={handleCopy}
2815
+ installedComponents={installedComponents}
2816
+ inspectorEnabled={inspectorEnabled}
2817
+ onToggleInspector={toggleInspector}
2818
+ config={config}
2819
+ updateConfig={updateConfig}
2820
+ onReset={handleReset}
2821
+ taggedElements={taggedElements}
2822
+ selectedComponentType={selectedComponentType}
2823
+ onSelectComponentType={handleSelectComponentType}
2824
+ componentScope={componentScope}
2825
+ onScopeChange={setComponentScope}
2826
+ selectedComponentId={selectedComponentId}
2827
+ onSelectComponent={setSelectedComponentId}
2828
+ selectedVariantId={selectedVariantId}
2829
+ onSelectVariant={setSelectedVariantId}
2830
+ analysisResult={analysisResult}
2831
+ componentOverrides={componentOverrides}
2832
+ onUpdateComponentOverride={updateComponentOverride}
2833
+ onResetComponentOverrides={resetComponentOverrides}
2834
+ viewMode={viewMode}
2835
+ onViewModeChange={setViewMode}
2836
+ isPreviewActive={isPreviewActive}
2837
+ onPreviewActiveChange={setIsPreviewActive}
2838
+ visionMode={visionModeActive}
2839
+ visionFocusedElements={visionFocusedElements}
2840
+ onVisionEditComplete={handleVisionEditComplete}
2841
+ visionPendingEdit={visionPendingEdit}
2842
+ onSaveVisionEdit={handleSaveVisionEdit}
2843
+ onClearVisionPendingEdit={handleClearVisionPendingEdit}
2844
+ // Apply-First Mode props
2845
+ applyFirstSession={applyFirstSession}
2846
+ applyFirstStatus={applyFirstStatus}
2847
+ onApplyFirstComplete={handleApplyFirstComplete}
2848
+ onApplyFirstAccept={handleApplyFirstAccept}
2849
+ onApplyFirstRevert={handleApplyFirstRevert}
2850
+ onApplyFirstForceClear={handleApplyFirstForceClear}
2851
+ // Element filter context - for showing filter-aware content
2852
+ elementFilters={elementFilters}
2853
+ selectedLogoId={selectedLogoId}
2854
+ onSelectLogo={handleSelectLogo}
2855
+ selectedTextId={selectedTextId}
2856
+ onSelectText={handleSelectText}
2857
+ />
2858
+ </div>
2859
+ )}
2860
+ </div>
2861
+ )}
2811
2862
  </div>
2812
2863
  );
2864
+
2865
+ // Floating trigger when closed (for re-opening)
2866
+ const closedTrigger = !isOpen && (
2867
+ <button
2868
+ onClick={() => setIsOpen(true)}
2869
+ className={cn(
2870
+ "fixed bottom-6 right-6 z-[2147483646] pointer-events-auto",
2871
+ "flex items-center gap-2 px-4 py-2.5",
2872
+ "rounded-full shadow-2xl",
2873
+ "backdrop-blur-xl bg-black/80 border border-white/10",
2874
+ "text-white font-medium text-sm",
2875
+ "hover:scale-105 hover:shadow-[0_0_30px_rgba(0,211,200,0.3)]",
2876
+ "transition-all duration-200",
2877
+ "focus:outline-none focus:ring-2 focus:ring-[#00D3C8] focus:ring-offset-2 focus:ring-offset-black"
2878
+ )}
2879
+ aria-label="Open Sonance DevTools"
2880
+ >
2881
+ <Palette className="h-4 w-4 text-[#00D3C8]" />
2882
+ <span>DevTools</span>
2883
+ </button>
2884
+ );
2813
2885
 
2814
2886
  // Wait for portal container to be ready
2815
2887
  if (!portalContainer) return null;
2816
2888
 
2817
2889
  return createPortal(
2818
2890
  <>
2819
- {trigger}
2820
- {panel}
2891
+ {/* Closed state trigger */}
2892
+ {closedTrigger}
2893
+ {/* Frame layout: Top Banner + Right Sidebar */}
2894
+ {topBanner}
2895
+ {sidebar}
2821
2896
  {/* Vision Mode Border Overlay */}
2822
2897
  <VisionModeBorder
2823
2898
  active={visionModeActive}
2824
2899
  focusedCount={visionFocusedElements.length}
2900
+ highlightEnabled={inspectorEnabled}
2825
2901
  />
2826
2902
  {/* Section Highlight - shows detected parent section when element is clicked */}
2827
2903
  <SectionHighlight
2828
2904
  active={visionModeActive}
2829
2905
  focusedElements={visionFocusedElements}
2830
2906
  />
2831
- {/* Visual Inspector Overlay - switches to preview mode when AI changes are pending */}
2832
- {(inspectorEnabled || isPreviewActive || visionModeActive || changedElements.length > 0 || (viewMode === "inspector" && selectedComponentType !== "all")) && filteredOverlayElements.length > 0 && (
2907
+ {/* Visual Inspector Overlay - shows when highlighting is enabled in Vision Mode or for previews */}
2908
+ {((visionModeActive && inspectorEnabled) || isPreviewActive || changedElements.length > 0 || (viewMode === "inspector" && selectedComponentType !== "all")) && filteredOverlayElements.length > 0 && (
2833
2909
  <InspectorOverlay
2834
2910
  elements={filteredOverlayElements}
2835
2911
  selectedLogoId={selectedLogoId}
2836
2912
  onLogoClick={handleSelectLogo}
2837
2913
  selectedTextId={selectedTextId}
2838
2914
  onTextClick={handleSelectText}
2839
- interactive={activeTab === "logos" || activeTab === "text"}
2915
+ interactive={activeTab === "elements" && (elementFilters.images || elementFilters.text)}
2840
2916
  selectedComponentId={selectedComponentId}
2841
2917
  onComponentClick={setSelectedComponentId}
2842
2918
  componentSelectionMode={componentScope === "selected" && inspectorEnabled}
2843
- // Inspector-first mode: clicking a component selects its type (allow continuous selection)
2844
- inspectorClickMode={inspectorEnabled && !isPreviewActive && !visionModeActive}
2919
+ // Vision Mode with highlighting: clicking a component selects its type
2920
+ inspectorClickMode={visionModeActive && inspectorEnabled && !isPreviewActive}
2845
2921
  onSelectComponentAndClose={handleInspectorSelectAndClose}
2846
2922
  // Pass selected type to highlight only matching components
2847
2923
  selectedComponentType={selectedComponentType}