sonance-brand-mcp 1.3.109 → 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.
Files changed (34) hide show
  1. package/dist/assets/api/sonance-ai-edit/route.ts +30 -7
  2. package/dist/assets/api/sonance-vision-apply/route.ts +33 -8
  3. package/dist/assets/api/sonance-vision-edit/route.ts +33 -8
  4. package/dist/assets/components/alert.tsx +35 -9
  5. package/dist/assets/components/badge.tsx +49 -20
  6. package/dist/assets/components/button.tsx +29 -20
  7. package/dist/assets/components/card.tsx +87 -33
  8. package/dist/assets/components/checkbox.tsx +36 -12
  9. package/dist/assets/components/dialog.tsx +73 -30
  10. package/dist/assets/components/dropdown-menu.tsx +57 -20
  11. package/dist/assets/components/input.tsx +35 -14
  12. package/dist/assets/components/pagination.tsx +86 -35
  13. package/dist/assets/components/popover.tsx +80 -36
  14. package/dist/assets/components/radio-group.tsx +40 -12
  15. package/dist/assets/components/select.tsx +62 -26
  16. package/dist/assets/components/switch.tsx +41 -13
  17. package/dist/assets/components/tabs.tsx +32 -12
  18. package/dist/assets/components/tooltip.tsx +34 -5
  19. package/dist/assets/dev-tools/SonanceDevTools.tsx +441 -365
  20. package/dist/assets/dev-tools/components/ChatHistory.tsx +141 -0
  21. package/dist/assets/dev-tools/components/ChatInterface.tsx +402 -294
  22. package/dist/assets/dev-tools/components/ChatTabBar.tsx +82 -0
  23. package/dist/assets/dev-tools/components/InlineDiffPreview.tsx +204 -0
  24. package/dist/assets/dev-tools/components/InspectorOverlay.tsx +12 -9
  25. package/dist/assets/dev-tools/components/PropertiesPanel.tsx +695 -0
  26. package/dist/assets/dev-tools/components/VisionModeBorder.tsx +16 -7
  27. package/dist/assets/dev-tools/constants.ts +38 -6
  28. package/dist/assets/dev-tools/hooks/useComputedStyles.ts +365 -0
  29. package/dist/assets/dev-tools/index.ts +3 -0
  30. package/dist/assets/dev-tools/panels/AnalysisPanel.tsx +32 -32
  31. package/dist/assets/dev-tools/panels/ComponentsPanel.tsx +277 -127
  32. package/dist/assets/dev-tools/types.ts +51 -2
  33. package/dist/index.js +22 -3
  34. package/package.json +2 -1
@@ -7,9 +7,14 @@ import { Eye } from "lucide-react";
7
7
  interface VisionModeBorderProps {
8
8
  active: boolean;
9
9
  focusedCount?: number;
10
+ highlightEnabled?: boolean;
10
11
  }
11
12
 
12
- export function VisionModeBorder({ active, focusedCount = 0 }: VisionModeBorderProps) {
13
+ export function VisionModeBorder({
14
+ active,
15
+ focusedCount = 0,
16
+ highlightEnabled = false,
17
+ }: VisionModeBorderProps) {
13
18
  const [mounted, setMounted] = useState(false);
14
19
 
15
20
  useEffect(() => {
@@ -39,20 +44,21 @@ export function VisionModeBorder({ active, focusedCount = 0 }: VisionModeBorderP
39
44
  }
40
45
  `}</style>
41
46
 
42
- {/* Main border overlay */}
47
+ {/* Main border overlay - respects devtools frame if present */}
43
48
  <div
44
49
  data-vision-mode-border="true"
45
50
  style={{
46
51
  position: "fixed",
47
- top: 0,
52
+ top: "var(--devtools-banner-height, 0px)",
48
53
  left: 0,
49
- right: 0,
54
+ right: "var(--devtools-sidebar-width, 0px)",
50
55
  bottom: 0,
51
56
  pointerEvents: "none",
52
57
  zIndex: 9996, // Below DevTools (9999) but above content
53
58
  border: "4px solid #8B5CF6",
54
59
  boxShadow: "inset 0 0 30px rgba(139, 92, 246, 0.3), 0 0 30px rgba(139, 92, 246, 0.3)",
55
60
  animation: "vision-pulse 2s ease-in-out infinite, vision-glow 2s ease-in-out infinite",
61
+ transition: "all 0.2s ease-out",
56
62
  }}
57
63
  >
58
64
  {/* Corner indicator */}
@@ -70,7 +76,7 @@ export function VisionModeBorder({ active, focusedCount = 0 }: VisionModeBorderP
70
76
  borderRadius: "6px",
71
77
  fontSize: "12px",
72
78
  fontWeight: 600,
73
- fontFamily: "system-ui, -apple-system, sans-serif",
79
+ fontFamily: "Montserrat, system-ui, -apple-system, sans-serif",
74
80
  boxShadow: "0 2px 8px rgba(139, 92, 246, 0.4)",
75
81
  }}
76
82
  >
@@ -102,12 +108,15 @@ export function VisionModeBorder({ active, focusedCount = 0 }: VisionModeBorderP
102
108
  padding: "8px 16px",
103
109
  borderRadius: "8px",
104
110
  fontSize: "13px",
105
- fontFamily: "system-ui, -apple-system, sans-serif",
111
+ fontFamily: "Montserrat, system-ui, -apple-system, sans-serif",
106
112
  boxShadow: "0 2px 12px rgba(139, 92, 246, 0.5)",
107
113
  whiteSpace: "nowrap",
108
114
  }}
109
115
  >
110
- Click elements to focus AI attention, then describe your changes in the chat
116
+ {highlightEnabled
117
+ ? "Click elements to focus AI attention, then describe your changes"
118
+ : "Toggle highlighting in the banner to select elements"
119
+ }
111
120
  </div>
112
121
  </div>
113
122
  </>,
@@ -1,13 +1,45 @@
1
1
 
2
2
  import { Box, Image as ImageIcon, Type, Scan } from "lucide-react";
3
- import { TabDefinition, ConfigSection } from "./types";
3
+ import { TabDefinition, ConfigSection, ElementFilters } from "./types";
4
4
  import { componentSnippets } from "../../lib/brand-system";
5
5
 
6
- export const tabs: TabDefinition[] = [
7
- { id: "components", label: "Components", icon: Box },
8
- { id: "logos", label: "Logos", icon: ImageIcon },
9
- { id: "text", label: "Text", icon: Type },
10
- ];
6
+ // Note: Tabs have been simplified - elements are now unified with filters
7
+ // Analysis is the only separate "tab" mode
8
+
9
+ // Default element filters - all enabled by default
10
+ export const DEFAULT_ELEMENT_FILTERS: ElementFilters = {
11
+ components: true,
12
+ images: true,
13
+ text: true,
14
+ };
15
+
16
+ // Element type color scheme for the unified view
17
+ export const ELEMENT_TYPE_COLORS = {
18
+ components: {
19
+ border: "#8B5CF6", // Purple
20
+ bg: "rgba(139, 92, 246, 0.1)",
21
+ activeBg: "bg-purple-100",
22
+ activeText: "text-purple-700",
23
+ activeBorder: "border-purple-300",
24
+ label: "Components",
25
+ },
26
+ images: {
27
+ border: "#3B82F6", // Blue
28
+ bg: "rgba(59, 130, 246, 0.1)",
29
+ activeBg: "bg-blue-100",
30
+ activeText: "text-blue-700",
31
+ activeBorder: "border-blue-300",
32
+ label: "Images",
33
+ },
34
+ text: {
35
+ border: "#F59E0B", // Amber
36
+ bg: "rgba(245, 158, 11, 0.1)",
37
+ activeBg: "bg-amber-100",
38
+ activeText: "text-amber-700",
39
+ activeBorder: "border-amber-300",
40
+ label: "Text",
41
+ },
42
+ };
11
43
 
12
44
  // Quick action presets for common styling changes
13
45
  export const QUICK_ACTIONS = [
@@ -0,0 +1,365 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect, useCallback } from "react";
4
+
5
+ export interface ComputedGeometry {
6
+ x: number;
7
+ y: number;
8
+ width: number;
9
+ height: number;
10
+ rotation: number;
11
+ }
12
+
13
+ export interface ComputedTypography {
14
+ fontFamily: string;
15
+ fontSize: string;
16
+ fontWeight: string;
17
+ lineHeight: string;
18
+ letterSpacing: string;
19
+ textAlign: string;
20
+ color: string;
21
+ }
22
+
23
+ export interface ComputedFill {
24
+ type: "solid" | "gradient" | "image";
25
+ color?: string;
26
+ opacity: number;
27
+ }
28
+
29
+ export interface ComputedStroke {
30
+ color: string;
31
+ width: string;
32
+ style: string;
33
+ }
34
+
35
+ export interface ComputedEffect {
36
+ type: "shadow" | "blur";
37
+ value: string;
38
+ }
39
+
40
+ export interface ComputedStyles {
41
+ // Element Info
42
+ tagName: string;
43
+ className: string;
44
+ id: string;
45
+
46
+ // Geometry (Layout)
47
+ geometry: ComputedGeometry;
48
+
49
+ // Appearance
50
+ opacity: number;
51
+ borderRadius: string;
52
+ overflow: string;
53
+
54
+ // Typography (for text elements)
55
+ typography: ComputedTypography | null;
56
+ hasText: boolean;
57
+ textContent: string;
58
+
59
+ // Fills (backgrounds)
60
+ fills: ComputedFill[];
61
+
62
+ // Strokes (borders)
63
+ strokes: ComputedStroke[];
64
+
65
+ // Effects (shadows, filters)
66
+ effects: ComputedEffect[];
67
+
68
+ // Display & Layout
69
+ display: string;
70
+ flexDirection: string;
71
+ alignItems: string;
72
+ justifyContent: string;
73
+ gap: string;
74
+ padding: string;
75
+ margin: string;
76
+ }
77
+
78
+ function parseColor(color: string): { color: string; opacity: number } {
79
+ // Handle rgba
80
+ const rgbaMatch = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
81
+ if (rgbaMatch) {
82
+ const [, r, g, b, a] = rgbaMatch;
83
+ const hex = `#${parseInt(r).toString(16).padStart(2, '0')}${parseInt(g).toString(16).padStart(2, '0')}${parseInt(b).toString(16).padStart(2, '0')}`;
84
+ return { color: hex.toUpperCase(), opacity: a ? parseFloat(a) : 1 };
85
+ }
86
+
87
+ // Handle hex
88
+ if (color.startsWith('#')) {
89
+ return { color: color.toUpperCase(), opacity: 1 };
90
+ }
91
+
92
+ // Handle transparent
93
+ if (color === 'transparent') {
94
+ return { color: 'transparent', opacity: 0 };
95
+ }
96
+
97
+ return { color, opacity: 1 };
98
+ }
99
+
100
+ function extractFills(styles: CSSStyleDeclaration): ComputedFill[] {
101
+ const fills: ComputedFill[] = [];
102
+
103
+ const bg = styles.backgroundColor;
104
+ if (bg && bg !== 'transparent' && bg !== 'rgba(0, 0, 0, 0)') {
105
+ const { color, opacity } = parseColor(bg);
106
+ fills.push({ type: 'solid', color, opacity });
107
+ }
108
+
109
+ const bgImage = styles.backgroundImage;
110
+ if (bgImage && bgImage !== 'none') {
111
+ if (bgImage.includes('gradient')) {
112
+ fills.push({ type: 'gradient', color: bgImage, opacity: 1 });
113
+ } else if (bgImage.includes('url')) {
114
+ fills.push({ type: 'image', opacity: 1 });
115
+ }
116
+ }
117
+
118
+ return fills;
119
+ }
120
+
121
+ function extractStrokes(styles: CSSStyleDeclaration): ComputedStroke[] {
122
+ const strokes: ComputedStroke[] = [];
123
+
124
+ const borderWidth = styles.borderWidth;
125
+ const borderColor = styles.borderColor;
126
+ const borderStyle = styles.borderStyle;
127
+
128
+ if (borderWidth && borderWidth !== '0px' && borderStyle !== 'none') {
129
+ const { color } = parseColor(borderColor);
130
+ strokes.push({
131
+ color,
132
+ width: borderWidth,
133
+ style: borderStyle,
134
+ });
135
+ }
136
+
137
+ return strokes;
138
+ }
139
+
140
+ function extractEffects(styles: CSSStyleDeclaration): ComputedEffect[] {
141
+ const effects: ComputedEffect[] = [];
142
+
143
+ const boxShadow = styles.boxShadow;
144
+ if (boxShadow && boxShadow !== 'none') {
145
+ effects.push({ type: 'shadow', value: boxShadow });
146
+ }
147
+
148
+ const filter = styles.filter;
149
+ if (filter && filter !== 'none') {
150
+ effects.push({ type: 'blur', value: filter });
151
+ }
152
+
153
+ return effects;
154
+ }
155
+
156
+ function isTextElement(element: HTMLElement): boolean {
157
+ const textTags = ['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'SPAN', 'A', 'LABEL', 'LI', 'TD', 'TH', 'BUTTON'];
158
+ if (textTags.includes(element.tagName)) return true;
159
+
160
+ // Check if element has direct text content
161
+ const hasDirectText = Array.from(element.childNodes).some(
162
+ node => node.nodeType === Node.TEXT_NODE && node.textContent?.trim()
163
+ );
164
+
165
+ return hasDirectText;
166
+ }
167
+
168
+ export function useComputedStyles(elementId: string | null, variantId?: string | null): ComputedStyles | null {
169
+ const [styles, setStyles] = useState<ComputedStyles | null>(null);
170
+
171
+ const extractStyles = useCallback(() => {
172
+ if (!elementId) {
173
+ setStyles(null);
174
+ return;
175
+ }
176
+
177
+ // Try to find element by ID first, then by data attributes
178
+ let element: HTMLElement | null = document.getElementById(elementId);
179
+
180
+ if (!element) {
181
+ // Try finding by component type and variant
182
+ const selector = variantId
183
+ ? `[data-component-type="${elementId}"][data-variant-id="${variantId}"]`
184
+ : `[data-component-type="${elementId}"]`;
185
+ element = document.querySelector(selector);
186
+ }
187
+
188
+ if (!element) {
189
+ // Try finding any element with matching text content or class
190
+ const allElements = document.querySelectorAll('button, a, [role="button"], input, select, textarea, h1, h2, h3, h4, h5, h6, p, span');
191
+ for (const el of allElements) {
192
+ if (el.id === elementId || el.className.includes(elementId)) {
193
+ element = el as HTMLElement;
194
+ break;
195
+ }
196
+ }
197
+ }
198
+
199
+ if (!element) {
200
+ setStyles(null);
201
+ return;
202
+ }
203
+
204
+ const computed = window.getComputedStyle(element);
205
+ const rect = element.getBoundingClientRect();
206
+
207
+ // Extract rotation from transform matrix
208
+ let rotation = 0;
209
+ const transform = computed.transform;
210
+ if (transform && transform !== 'none') {
211
+ const matrixMatch = transform.match(/matrix\(([^)]+)\)/);
212
+ if (matrixMatch) {
213
+ const values = matrixMatch[1].split(',').map(v => parseFloat(v.trim()));
214
+ rotation = Math.round(Math.atan2(values[1], values[0]) * (180 / Math.PI));
215
+ }
216
+ }
217
+
218
+ const hasText = isTextElement(element);
219
+ const textContent = element.textContent?.trim().substring(0, 50) || '';
220
+
221
+ const computedStyles: ComputedStyles = {
222
+ tagName: element.tagName.toLowerCase(),
223
+ className: element.className?.toString() || '',
224
+ id: element.id || '',
225
+
226
+ geometry: {
227
+ x: Math.round(rect.left),
228
+ y: Math.round(rect.top),
229
+ width: Math.round(rect.width),
230
+ height: Math.round(rect.height),
231
+ rotation,
232
+ },
233
+
234
+ opacity: parseFloat(computed.opacity) * 100,
235
+ borderRadius: computed.borderRadius,
236
+ overflow: computed.overflow,
237
+
238
+ typography: hasText ? {
239
+ fontFamily: computed.fontFamily.split(',')[0].replace(/['"]/g, ''),
240
+ fontSize: computed.fontSize,
241
+ fontWeight: computed.fontWeight,
242
+ lineHeight: computed.lineHeight,
243
+ letterSpacing: computed.letterSpacing,
244
+ textAlign: computed.textAlign,
245
+ color: parseColor(computed.color).color,
246
+ } : null,
247
+ hasText,
248
+ textContent,
249
+
250
+ fills: extractFills(computed),
251
+ strokes: extractStrokes(computed),
252
+ effects: extractEffects(computed),
253
+
254
+ display: computed.display,
255
+ flexDirection: computed.flexDirection,
256
+ alignItems: computed.alignItems,
257
+ justifyContent: computed.justifyContent,
258
+ gap: computed.gap,
259
+ padding: computed.padding,
260
+ margin: computed.margin,
261
+ };
262
+
263
+ setStyles(computedStyles);
264
+ }, [elementId, variantId]);
265
+
266
+ useEffect(() => {
267
+ extractStyles();
268
+
269
+ // Re-extract on resize/scroll
270
+ const handleUpdate = () => extractStyles();
271
+ window.addEventListener('resize', handleUpdate);
272
+ window.addEventListener('scroll', handleUpdate, true);
273
+
274
+ return () => {
275
+ window.removeEventListener('resize', handleUpdate);
276
+ window.removeEventListener('scroll', handleUpdate, true);
277
+ };
278
+ }, [extractStyles]);
279
+
280
+ return styles;
281
+ }
282
+
283
+ // Simpler version that works with VisionFocusedElement coordinates
284
+ export function useElementFromCoordinates(coordinates: { x: number; y: number; width: number; height: number } | null): ComputedStyles | null {
285
+ const [styles, setStyles] = useState<ComputedStyles | null>(null);
286
+
287
+ useEffect(() => {
288
+ if (!coordinates) {
289
+ setStyles(null);
290
+ return;
291
+ }
292
+
293
+ // Find element at center of coordinates
294
+ const centerX = coordinates.x + coordinates.width / 2;
295
+ const centerY = coordinates.y + coordinates.height / 2;
296
+
297
+ const element = document.elementFromPoint(centerX, centerY) as HTMLElement;
298
+
299
+ if (!element || element.closest('[data-sonance-devtools="true"]')) {
300
+ setStyles(null);
301
+ return;
302
+ }
303
+
304
+ const computed = window.getComputedStyle(element);
305
+ const rect = element.getBoundingClientRect();
306
+
307
+ let rotation = 0;
308
+ const transform = computed.transform;
309
+ if (transform && transform !== 'none') {
310
+ const matrixMatch = transform.match(/matrix\(([^)]+)\)/);
311
+ if (matrixMatch) {
312
+ const values = matrixMatch[1].split(',').map(v => parseFloat(v.trim()));
313
+ rotation = Math.round(Math.atan2(values[1], values[0]) * (180 / Math.PI));
314
+ }
315
+ }
316
+
317
+ const hasText = isTextElement(element);
318
+ const textContent = element.textContent?.trim().substring(0, 50) || '';
319
+
320
+ setStyles({
321
+ tagName: element.tagName.toLowerCase(),
322
+ className: element.className?.toString() || '',
323
+ id: element.id || '',
324
+
325
+ geometry: {
326
+ x: Math.round(rect.left),
327
+ y: Math.round(rect.top),
328
+ width: Math.round(rect.width),
329
+ height: Math.round(rect.height),
330
+ rotation,
331
+ },
332
+
333
+ opacity: parseFloat(computed.opacity) * 100,
334
+ borderRadius: computed.borderRadius,
335
+ overflow: computed.overflow,
336
+
337
+ typography: hasText ? {
338
+ fontFamily: computed.fontFamily.split(',')[0].replace(/['"]/g, ''),
339
+ fontSize: computed.fontSize,
340
+ fontWeight: computed.fontWeight,
341
+ lineHeight: computed.lineHeight,
342
+ letterSpacing: computed.letterSpacing,
343
+ textAlign: computed.textAlign,
344
+ color: parseColor(computed.color).color,
345
+ } : null,
346
+ hasText,
347
+ textContent,
348
+
349
+ fills: extractFills(computed),
350
+ strokes: extractStrokes(computed),
351
+ effects: extractEffects(computed),
352
+
353
+ display: computed.display,
354
+ flexDirection: computed.flexDirection,
355
+ alignItems: computed.alignItems,
356
+ justifyContent: computed.justifyContent,
357
+ gap: computed.gap,
358
+ padding: computed.padding,
359
+ margin: computed.margin,
360
+ });
361
+ }, [coordinates]);
362
+
363
+ return styles;
364
+ }
365
+
@@ -21,6 +21,9 @@ export {
21
21
  export { Section, ColorSwatch, SelectField } from "./components/common";
22
22
  export { InspectorOverlay } from "./components/InspectorOverlay";
23
23
  export { ChatInterface } from "./components/ChatInterface";
24
+ export { ChatTabBar } from "./components/ChatTabBar";
25
+ export { ChatHistory } from "./components/ChatHistory";
26
+ export { InlineDiffPreview } from "./components/InlineDiffPreview";
24
27
  export { DiffPreview } from "./components/DiffPreview";
25
28
 
26
29
  // Panels
@@ -25,24 +25,24 @@ export function AnalysisPanel({
25
25
  onBulkTag,
26
26
  }: AnalysisPanelProps) {
27
27
  return (
28
- <div className="space-y-4">
28
+ <div className="space-y-3">
29
29
  {/* Initial State */}
30
30
  {analysisStatus === "idle" && !analysisResult && (
31
- <div className="text-center py-6 space-y-4">
32
- <div className="mx-auto w-14 h-14 rounded-full bg-gray-100 flex items-center justify-center">
33
- <Scan className="h-7 w-7 text-gray-400" />
31
+ <div className="text-center py-4 space-y-3">
32
+ <div className="mx-auto w-10 h-10 rounded-full bg-gray-100 flex items-center justify-center">
33
+ <Scan className="h-5 w-5 text-gray-400" />
34
34
  </div>
35
35
  <div>
36
- <h3 id="analysis-panel-h3-analyze-your-project" className="text-base font-medium">Analyze Your Project</h3>
37
- <p id="analysis-panel-p-scan-your-codebase-t" className="text-xs text-gray-500 mt-1">
38
- Scan your codebase to index all images, logos, and theme files.
36
+ <h3 id="analysis-panel-h3-analyze-your-project" className="text-sm font-medium">Analyze Project</h3>
37
+ <p id="analysis-panel-p-scan-your-codebase-t" className="text-[11px] text-gray-500 mt-0.5">
38
+ Index images, logos, and theme files.
39
39
  </p>
40
40
  </div>
41
41
  <button
42
42
  onClick={onRunAnalysis}
43
- className="inline-flex items-center gap-2 px-4 py-2 bg-[#333F48] text-white text-sm font-medium rounded hover:bg-[#2a343c] transition-colors"
43
+ className="inline-flex items-center gap-1.5 px-3 py-1.5 bg-[#333F48] text-white text-xs font-medium rounded hover:bg-[#2a343c] transition-colors"
44
44
  >
45
- <Scan className="h-4 w-4" />
45
+ <Scan className="h-3.5 w-3.5" />
46
46
  Scan Project
47
47
  </button>
48
48
  </div>
@@ -50,14 +50,14 @@ export function AnalysisPanel({
50
50
 
51
51
  {/* Scanning State */}
52
52
  {analysisStatus === "scanning" && (
53
- <div className="text-center py-6 space-y-4">
54
- <div className="mx-auto w-14 h-14 rounded-full bg-blue-50 flex items-center justify-center">
55
- <Loader2 className="h-7 w-7 text-[#00A3E1] animate-spin" />
53
+ <div className="text-center py-4 space-y-3">
54
+ <div className="mx-auto w-10 h-10 rounded-full bg-blue-50 flex items-center justify-center">
55
+ <Loader2 className="h-5 w-5 text-[#00A3E1] animate-spin" />
56
56
  </div>
57
57
  <div>
58
- <h3 id="analysis-panel-h3-scanning" className="text-base font-medium">Scanning...</h3>
59
- <p id="analysis-panel-p-analyzing-your-sourc" className="text-xs text-gray-500 mt-1">
60
- Analyzing your source files for design assets.
58
+ <h3 id="analysis-panel-h3-scanning" className="text-sm font-medium">Scanning...</h3>
59
+ <p id="analysis-panel-p-analyzing-your-sourc" className="text-[11px] text-gray-500 mt-0.5">
60
+ Analyzing source files.
61
61
  </p>
62
62
  </div>
63
63
  </div>
@@ -65,19 +65,19 @@ export function AnalysisPanel({
65
65
 
66
66
  {/* Error State */}
67
67
  {analysisStatus === "error" && (
68
- <div className="text-center py-6 space-y-4">
69
- <div className="mx-auto w-14 h-14 rounded-full bg-red-50 flex items-center justify-center">
70
- <AlertCircle className="h-7 w-7 text-red-500" />
68
+ <div className="text-center py-4 space-y-3">
69
+ <div className="mx-auto w-10 h-10 rounded-full bg-red-50 flex items-center justify-center">
70
+ <AlertCircle className="h-5 w-5 text-red-500" />
71
71
  </div>
72
72
  <div>
73
- <h3 id="analysis-panel-h3-analysis-failed" className="text-base font-medium text-red-600">Analysis Failed</h3>
74
- <p id="analysis-panel-p-analysiserror" className="text-xs text-gray-500 mt-1">{analysisError}</p>
73
+ <h3 id="analysis-panel-h3-analysis-failed" className="text-sm font-medium text-red-600">Analysis Failed</h3>
74
+ <p id="analysis-panel-p-analysiserror" className="text-[11px] text-gray-500 mt-0.5">{analysisError}</p>
75
75
  </div>
76
76
  <button
77
77
  onClick={onRunAnalysis}
78
- className="inline-flex items-center gap-2 px-4 py-2 bg-[#333F48] text-white text-sm font-medium rounded hover:bg-[#2a343c] transition-colors"
78
+ className="inline-flex items-center gap-1.5 px-3 py-1.5 bg-[#333F48] text-white text-xs font-medium rounded hover:bg-[#2a343c] transition-colors"
79
79
  >
80
- <RotateCcw className="h-4 w-4" />
80
+ <RotateCcw className="h-3.5 w-3.5" />
81
81
  Try Again
82
82
  </button>
83
83
  </div>
@@ -87,18 +87,18 @@ export function AnalysisPanel({
87
87
  {analysisStatus === "complete" && analysisResult && (
88
88
  <>
89
89
  {/* Overview Stats */}
90
- <div className="grid grid-cols-3 gap-2">
91
- <div className="p-2 rounded border border-gray-200 bg-gray-50 text-center">
92
- <div className="text-[10px] text-gray-500 uppercase tracking-wide">Files</div>
93
- <div className="text-lg font-semibold">{analysisResult.filesScanned}</div>
90
+ <div className="grid grid-cols-3 gap-1.5">
91
+ <div className="p-1.5 rounded border border-gray-200 bg-gray-50 text-center">
92
+ <div className="text-[9px] text-gray-500 uppercase tracking-wide">Files</div>
93
+ <div className="text-base font-semibold">{analysisResult.filesScanned}</div>
94
94
  </div>
95
- <div className="p-2 rounded border border-green-200 bg-green-50 text-center">
96
- <div className="text-[10px] text-green-600 uppercase tracking-wide">With IDs</div>
97
- <div className="text-lg font-semibold text-green-700">{analysisResult.summary.elementsWithId}</div>
95
+ <div className="p-1.5 rounded border border-green-200 bg-green-50 text-center">
96
+ <div className="text-[9px] text-green-600 uppercase tracking-wide">With IDs</div>
97
+ <div className="text-base font-semibold text-green-700">{analysisResult.summary.elementsWithId}</div>
98
98
  </div>
99
- <div className="p-2 rounded border border-amber-200 bg-amber-50 text-center">
100
- <div className="text-[10px] text-amber-600 uppercase tracking-wide">Missing</div>
101
- <div className="text-lg font-semibold text-amber-700">{analysisResult.summary.elementsMissingId}</div>
99
+ <div className="p-1.5 rounded border border-amber-200 bg-amber-50 text-center">
100
+ <div className="text-[9px] text-amber-600 uppercase tracking-wide">Missing</div>
101
+ <div className="text-base font-semibold text-amber-700">{analysisResult.summary.elementsMissingId}</div>
102
102
  </div>
103
103
  </div>
104
104