@promakeai/inspector 0.0.3 → 0.1.0

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.
package/dist/plugin.js CHANGED
@@ -61,13 +61,23 @@ export function inspectorPlugin() {
61
61
  let selectedElementData = null;
62
62
  let overlay = null;
63
63
  let controlBox = null;
64
+ let promakeBadge = null;
65
+
66
+ // Immediate update flags
67
+ let updateTextImmediately = false;
68
+ let updateImageImmediately = false;
64
69
 
65
70
  // Customizable labels
66
71
  let labels = {
67
72
  editText: 'Edit Text',
68
73
  textPlaceholder: 'Enter text...',
69
74
  updateText: 'Update',
70
- promptPlaceholder: 'Ask Promake for changes...'
75
+ promptPlaceholder: 'Ask Promake for changes...',
76
+ editImage: 'Change Image',
77
+ imageUploadTitle: 'Select Image',
78
+ imageUploadHint: 'Click or drag',
79
+ updateImage: 'Update Image',
80
+ badgeText: 'Built with Promake'
71
81
  };
72
82
 
73
83
  // Customizable theme colors
@@ -78,7 +88,10 @@ export function inspectorPlugin() {
78
88
  buttonTextColor: '#ffffff',
79
89
  inputBackgroundColor: '#f9fafb',
80
90
  inputTextColor: '#111827',
81
- inputBorderColor: '#d1d5db'
91
+ inputBorderColor: '#d1d5db',
92
+ badgeGradientStart: '#411E93',
93
+ badgeGradientEnd: '#E87C85',
94
+ badgeTextColor: '#ffffff'
82
95
  };
83
96
 
84
97
  // Create overlay for highlighting
@@ -114,37 +127,63 @@ export function inspectorPlugin() {
114
127
  const isTextElement = element.textContent && element.children.length === 0;
115
128
  const currentText = isTextElement ? element.textContent.trim() : '';
116
129
 
117
- // Store text info in data attribute for later use
130
+ // Check if element is an image
131
+ const isImageElement = element.tagName === 'IMG';
132
+ const currentImageUrl = isImageElement ? element.src : '';
133
+
134
+ // Store element info in data attributes for later use
118
135
  controlBox.setAttribute('data-is-text-element', isTextElement);
119
136
  controlBox.setAttribute('data-current-text', currentText);
137
+ controlBox.setAttribute('data-is-image-element', isImageElement);
138
+ controlBox.setAttribute('data-current-image-url', currentImageUrl);
120
139
 
121
- // Calculate position
122
- const boxWidth = Math.max(320, Math.min(rect.width, 500));
123
- const centerLeft = rect.left + (rect.width / 2) - (boxWidth / 2);
140
+ // Calculate position with viewport bounds
141
+ const viewportWidth = window.innerWidth;
124
142
  const viewportHeight = window.innerHeight;
143
+ const padding = 10; // Minimum padding from viewport edges
144
+
145
+ // Calculate box width (responsive, between 320 and 600px, but never wider than viewport)
146
+ const maxWidth = Math.min(600, viewportWidth - (padding * 2));
147
+ const minWidth = Math.min(320, maxWidth);
148
+ const boxWidth = Math.max(minWidth, Math.min(rect.width, maxWidth));
149
+
150
+ // Calculate horizontal position (centered on element, but within viewport)
151
+ let centerLeft = rect.left + (rect.width / 2) - (boxWidth / 2);
152
+ // Ensure box doesn't overflow left or right
153
+ centerLeft = Math.max(padding, Math.min(centerLeft, viewportWidth - boxWidth - padding));
154
+
155
+ // Calculate vertical position
125
156
  const spaceBelow = viewportHeight - rect.bottom;
126
157
  const spaceAbove = rect.top;
127
158
 
128
159
  // Estimate box height (only prompt input now, smaller)
129
160
  const estimatedBoxHeight = 100;
130
161
 
131
- // Show above if not enough space below
162
+ // Calculate available space and max height
163
+ const maxBoxHeight = Math.min(400, viewportHeight - (padding * 4)); // Max 400px or viewport - padding
164
+
165
+ // Determine if box should be above or below element
132
166
  let topPosition;
133
- if (spaceBelow < estimatedBoxHeight && spaceAbove > spaceBelow) {
134
- // Show above element (positioned from top, showing above element)
135
- topPosition = rect.top - estimatedBoxHeight - 10;
167
+ if (spaceBelow < estimatedBoxHeight + padding && spaceAbove > spaceBelow) {
168
+ // Show above element
169
+ topPosition = Math.max(padding, rect.top - maxBoxHeight - 10);
136
170
  controlBox.setAttribute('data-position', 'above');
137
171
  } else {
138
172
  // Show below element
139
173
  topPosition = rect.bottom + 10;
174
+ // Ensure box fits in viewport
175
+ if (topPosition + maxBoxHeight > viewportHeight - padding) {
176
+ topPosition = Math.max(padding, viewportHeight - maxBoxHeight - padding);
177
+ }
140
178
  controlBox.setAttribute('data-position', 'below');
141
179
  }
142
180
 
143
181
  controlBox.style.cssText = \`
144
182
  position: fixed;
145
183
  top: \${topPosition}px;
146
- left: \${Math.max(10, centerLeft)}px;
184
+ left: \${centerLeft}px;
147
185
  width: \${boxWidth}px;
186
+ max-height: \${maxBoxHeight}px;
148
187
  background: \${theme.backgroundColor};
149
188
  border: 1px solid #e5e7eb;
150
189
  border-radius: 14px;
@@ -156,6 +195,7 @@ export function inspectorPlugin() {
156
195
  display: flex;
157
196
  flex-direction: column;
158
197
  gap: 0;
198
+ overflow-y: auto;
159
199
  \`;
160
200
 
161
201
  // Only prompt input and buttons (always shown)
@@ -208,13 +248,14 @@ export function inspectorPlugin() {
208
248
  const prompt = promptInput.value.trim();
209
249
  if (prompt) {
210
250
  if (window.parent !== window) {
211
- window.parent.postMessage({
251
+ const message = JSON.stringify({
212
252
  type: 'INSPECTOR_PROMPT_SUBMITTED',
213
253
  data: {
214
254
  prompt: prompt,
215
255
  element: elementData
216
256
  }
217
- }, '*');
257
+ });
258
+ window.parent.postMessage(message, '*');
218
259
  }
219
260
  console.log('Prompt submitted:', elementData);
220
261
 
@@ -298,15 +339,37 @@ export function inspectorPlugin() {
298
339
  textSubmit.addEventListener('click', (e) => {
299
340
  e.stopPropagation();
300
341
  const newText = textInput.value;
342
+
343
+ // Update DOM immediately if requested
344
+ if (updateTextImmediately && selectedElement) {
345
+ // Find all text nodes and update them
346
+ const updateTextNodes = (element) => {
347
+ for (let i = 0; i < element.childNodes.length; i++) {
348
+ const node = element.childNodes[i];
349
+ if (node.nodeType === Node.TEXT_NODE && node.textContent.trim()) {
350
+ node.textContent = newText;
351
+ break; // Update only the first text node
352
+ } else if (node.nodeType === Node.ELEMENT_NODE) {
353
+ // Skip inspector elements
354
+ if (!node.id || !node.id.startsWith('inspector-')) {
355
+ updateTextNodes(node);
356
+ }
357
+ }
358
+ }
359
+ };
360
+ updateTextNodes(selectedElement);
361
+ }
362
+
301
363
  if (window.parent !== window) {
302
- window.parent.postMessage({
364
+ const message = JSON.stringify({
303
365
  type: 'INSPECTOR_TEXT_UPDATED',
304
366
  data: {
305
367
  text: newText,
306
368
  originalText: currentText,
307
369
  element: selectedElementData
308
370
  }
309
- }, '*');
371
+ });
372
+ window.parent.postMessage(message, '*');
310
373
  }
311
374
  console.log('Text updated:', newText);
312
375
 
@@ -345,31 +408,276 @@ export function inspectorPlugin() {
345
408
  }
346
409
  }
347
410
 
411
+ // Show or hide image input section
412
+ function toggleImageInput(show) {
413
+ if (!controlBox) return;
414
+
415
+ const isImageElement = controlBox.getAttribute('data-is-image-element') === 'true';
416
+ const currentImageUrl = controlBox.getAttribute('data-current-image-url') || '';
417
+
418
+ // Check if image input already exists
419
+ let imageSection = controlBox.querySelector('#inspector-image-section');
420
+
421
+ if (show && !imageSection && isImageElement) {
422
+ // Create image input section
423
+ imageSection = document.createElement('div');
424
+ imageSection.id = 'inspector-image-section';
425
+ imageSection.style.cssText = 'margin-bottom: 12px;';
426
+
427
+ imageSection.innerHTML = \`
428
+ <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px;">
429
+ <label style="font-weight: 500; color: \${theme.textColor}; font-size: 13px;">
430
+ \${labels.editImage}
431
+ </label>
432
+ </div>
433
+ <input
434
+ type="file"
435
+ id="inspector-image-input"
436
+ accept="image/*"
437
+ style="display: none;"
438
+ />
439
+ <div
440
+ id="inspector-image-upload-box"
441
+ style="
442
+ width: 100%;
443
+ min-height: 100px;
444
+ border: 2px dashed \${theme.inputBorderColor};
445
+ border-radius: 12px;
446
+ background: \${theme.inputBackgroundColor};
447
+ cursor: pointer;
448
+ transition: all 0.2s;
449
+ display: flex;
450
+ align-items: center;
451
+ justify-content: center;
452
+ flex-direction: column;
453
+ gap: 8px;
454
+ padding: 16px;
455
+ margin-bottom: 8px;
456
+ position: relative;
457
+ overflow: hidden;
458
+ "
459
+ onmouseover="this.style.borderColor='#9ca3af'; this.style.background='#ffffff';"
460
+ onmouseout="this.style.borderColor='\${theme.inputBorderColor}'; this.style.background='\${theme.inputBackgroundColor}';"
461
+ >
462
+ <div id="inspector-upload-placeholder" style="text-align: center; pointer-events: none;">
463
+ <svg width="32" height="32" viewBox="0 0 24 24" fill="none" style="margin: 0 auto 8px; opacity: 0.5;">
464
+ <path d="M21 15V19C21 19.5304 20.7893 20.0391 20.4142 20.4142C20.0391 20.7893 19.5304 21 19 21H5C4.46957 21 3.96086 20.7893 3.58579 20.4142C3.21071 20.0391 3 19.5304 3 19V15M17 8L12 3M12 3L7 8M12 3V15" stroke="#9ca3af" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
465
+ </svg>
466
+ <div style="font-size: 13px; color: #6b7280; font-weight: 500;">\${labels.imageUploadTitle}</div>
467
+ <div style="font-size: 11px; color: #9ca3af; margin-top: 2px;">\${labels.imageUploadHint}</div>
468
+ </div>
469
+ <img
470
+ id="inspector-image-preview-img"
471
+ src=""
472
+ alt="Preview"
473
+ style="
474
+ display: none;
475
+ width: 100%;
476
+ height: auto;
477
+ max-height: 100px;
478
+ object-fit: contain;
479
+ border-radius: 8px;
480
+ pointer-events: none;
481
+ "
482
+ />
483
+ </div>
484
+ <div style="margin-top: 8px;">
485
+ <button
486
+ id="inspector-image-submit"
487
+ style="width: 100%; padding: 10px 16px; background: \${theme.buttonColor}; color: \${theme.buttonTextColor}; border: none; border-radius: 14px; cursor: pointer; font-weight: 500; font-size: 14px; transition: all 0.2s; opacity: 0.5;"
488
+ onmouseover="if(!this.disabled) this.style.opacity='0.85';"
489
+ onmouseout="if(!this.disabled) this.style.opacity='1';"
490
+ title="\${labels.updateImage}"
491
+ disabled
492
+ >
493
+ \${labels.updateImage}
494
+ </button>
495
+ </div>
496
+ <div style="border-top: 1px solid #e5e7eb; margin-top: 12px; margin-bottom: 12px;"></div>
497
+ \`;
498
+
499
+ // Insert at the beginning of controlBox
500
+ const promptSection = controlBox.querySelector('#inspector-prompt-section');
501
+ controlBox.insertBefore(imageSection, promptSection);
502
+
503
+ // Add event listeners
504
+ const imageInput = imageSection.querySelector('#inspector-image-input');
505
+ const imageSubmit = imageSection.querySelector('#inspector-image-submit');
506
+ const uploadBox = imageSection.querySelector('#inspector-image-upload-box');
507
+ const uploadPlaceholder = imageSection.querySelector('#inspector-upload-placeholder');
508
+ const imagePreviewImg = imageSection.querySelector('#inspector-image-preview-img');
509
+
510
+ let selectedFile = null;
511
+ let imageData = null;
512
+
513
+ // Handle file selection
514
+ const handleFile = (file) => {
515
+ if (file && file.type.startsWith('image/')) {
516
+ selectedFile = file;
517
+
518
+ // Read file as base64
519
+ const reader = new FileReader();
520
+ reader.onload = (evt) => {
521
+ imageData = evt.target.result;
522
+ imagePreviewImg.src = imageData;
523
+ imagePreviewImg.style.display = 'block';
524
+ uploadPlaceholder.style.display = 'none';
525
+ imageSubmit.disabled = false;
526
+ imageSubmit.style.opacity = '1';
527
+ imageSubmit.style.cursor = 'pointer';
528
+ };
529
+ reader.readAsDataURL(file);
530
+ }
531
+ };
532
+
533
+ // Click to select file
534
+ uploadBox.addEventListener('click', (e) => {
535
+ e.stopPropagation();
536
+ imageInput.click();
537
+ });
538
+
539
+ // File input change
540
+ imageInput.addEventListener('change', (e) => {
541
+ e.stopPropagation();
542
+ const file = e.target.files[0];
543
+ handleFile(file);
544
+ });
545
+
546
+ // Drag and drop support
547
+ uploadBox.addEventListener('dragover', (e) => {
548
+ e.preventDefault();
549
+ e.stopPropagation();
550
+ uploadBox.style.borderColor = '#4417db';
551
+ uploadBox.style.background = '#f0ebff';
552
+ });
553
+
554
+ uploadBox.addEventListener('dragleave', (e) => {
555
+ e.preventDefault();
556
+ e.stopPropagation();
557
+ uploadBox.style.borderColor = theme.inputBorderColor;
558
+ uploadBox.style.background = theme.inputBackgroundColor;
559
+ });
560
+
561
+ uploadBox.addEventListener('drop', (e) => {
562
+ e.preventDefault();
563
+ e.stopPropagation();
564
+ uploadBox.style.borderColor = theme.inputBorderColor;
565
+ uploadBox.style.background = theme.inputBackgroundColor;
566
+
567
+ const file = e.dataTransfer.files[0];
568
+ handleFile(file);
569
+ });
570
+
571
+ // Submit image update
572
+ imageSubmit.addEventListener('click', (e) => {
573
+ e.stopPropagation();
574
+
575
+ if (!selectedFile || !imageData) return;
576
+
577
+ // Update DOM immediately if requested
578
+ if (updateImageImmediately && selectedElement) {
579
+ // If it's an img element, update src directly
580
+ if (selectedElement.tagName.toLowerCase() === 'img') {
581
+ selectedElement.src = imageData;
582
+ }
583
+ // If element has background-image, update it
584
+ else if (window.getComputedStyle(selectedElement).backgroundImage !== 'none') {
585
+ selectedElement.style.backgroundImage = \`url("\${imageData}")\`;
586
+ }
587
+ // Try to find img child
588
+ else {
589
+ const imgChild = selectedElement.querySelector('img');
590
+ if (imgChild) {
591
+ imgChild.src = imageData;
592
+ }
593
+ }
594
+ }
595
+
596
+ if (window.parent !== window) {
597
+ const message = JSON.stringify({
598
+ type: 'INSPECTOR_IMAGE_UPDATED',
599
+ data: {
600
+ imageData: imageData,
601
+ imageFile: {
602
+ name: selectedFile.name,
603
+ size: selectedFile.size,
604
+ type: selectedFile.type
605
+ },
606
+ originalImageUrl: currentImageUrl,
607
+ element: selectedElementData
608
+ }
609
+ });
610
+ window.parent.postMessage(message, '*');
611
+ }
612
+ console.log('Image updated:', selectedFile.name);
613
+
614
+ // Turn off inspect mode after updating
615
+ toggleInspectMode(false);
616
+ });
617
+
618
+ // Adjust box height
619
+ adjustControlBoxPosition();
620
+ } else if (!show && imageSection) {
621
+ // Remove image input section
622
+ imageSection.remove();
623
+
624
+ // Refocus prompt input
625
+ const promptInput = controlBox.querySelector('#inspector-prompt-input');
626
+ if (promptInput) {
627
+ setTimeout(() => promptInput.focus(), 100);
628
+ }
629
+
630
+ // Adjust box height
631
+ adjustControlBoxPosition();
632
+ }
633
+ }
634
+
348
635
  // Adjust control box position based on content
349
636
  function adjustControlBoxPosition() {
350
637
  if (!controlBox || !selectedElement) return;
351
638
 
352
639
  const rect = selectedElement.getBoundingClientRect();
353
- const boxWidth = Math.max(320, Math.min(rect.width, 500));
354
- const centerLeft = rect.left + (rect.width / 2) - (boxWidth / 2);
640
+ const viewportWidth = window.innerWidth;
355
641
  const viewportHeight = window.innerHeight;
642
+ const padding = 10;
643
+
644
+ // Calculate box width with viewport bounds
645
+ const maxWidth = Math.min(600, viewportWidth - (padding * 2));
646
+ const minWidth = Math.min(320, maxWidth);
647
+ const boxWidth = Math.max(minWidth, Math.min(rect.width, maxWidth));
648
+
649
+ // Calculate horizontal position with viewport bounds
650
+ let centerLeft = rect.left + (rect.width / 2) - (boxWidth / 2);
651
+ centerLeft = Math.max(padding, Math.min(centerLeft, viewportWidth - boxWidth - padding));
652
+
653
+ // Calculate vertical position
356
654
  const spaceBelow = viewportHeight - rect.bottom;
357
655
  const spaceAbove = rect.top;
358
656
 
359
- // Get actual box height
657
+ // Get actual box height and apply max height
360
658
  const boxHeight = controlBox.offsetHeight;
659
+ const maxBoxHeight = Math.min(400, viewportHeight - (padding * 4));
361
660
 
362
- // Show above if not enough space below
661
+ // Determine position with viewport bounds
363
662
  let topPosition;
364
- if (spaceBelow < boxHeight + 20 && spaceAbove > spaceBelow) {
365
- topPosition = rect.top - boxHeight - 10;
663
+ if (spaceBelow < boxHeight + padding && spaceAbove > spaceBelow) {
664
+ // Show above element
665
+ topPosition = Math.max(padding, rect.top - maxBoxHeight - 10);
366
666
  controlBox.setAttribute('data-position', 'above');
367
667
  } else {
668
+ // Show below element
368
669
  topPosition = rect.bottom + 10;
670
+ // Ensure box fits in viewport
671
+ if (topPosition + maxBoxHeight > viewportHeight - padding) {
672
+ topPosition = Math.max(padding, viewportHeight - maxBoxHeight - padding);
673
+ }
369
674
  controlBox.setAttribute('data-position', 'below');
370
675
  }
371
676
 
372
677
  controlBox.style.top = topPosition + 'px';
678
+ controlBox.style.left = centerLeft + 'px';
679
+ controlBox.style.width = boxWidth + 'px';
680
+ controlBox.style.maxHeight = maxBoxHeight + 'px';
373
681
  }
374
682
 
375
683
  // Pause inspection
@@ -499,6 +807,40 @@ export function inspectorPlugin() {
499
807
  highlightElement(hoveredElement);
500
808
  }
501
809
 
810
+ // Handle scroll - update highlight position
811
+ function handleScroll() {
812
+ if (!inspectMode) return;
813
+
814
+ if (isPaused && selectedElement) {
815
+ // Update overlay for selected element
816
+ highlightElement(selectedElement);
817
+ // Update control box position
818
+ if (controlBox) {
819
+ adjustControlBoxPosition();
820
+ }
821
+ } else if (hoveredElement) {
822
+ // Update overlay for hovered element
823
+ highlightElement(hoveredElement);
824
+ }
825
+ }
826
+
827
+ // Handle resize - update control box position
828
+ function handleResize() {
829
+ if (!inspectMode) return;
830
+
831
+ if (isPaused && selectedElement) {
832
+ // Update overlay for selected element
833
+ highlightElement(selectedElement);
834
+ // Update control box position
835
+ if (controlBox) {
836
+ adjustControlBoxPosition();
837
+ }
838
+ } else if (hoveredElement) {
839
+ // Update overlay for hovered element
840
+ highlightElement(hoveredElement);
841
+ }
842
+ }
843
+
502
844
  // Handle click
503
845
  function handleClick(e) {
504
846
  if (!inspectMode) return;
@@ -528,6 +870,11 @@ export function inspectorPlugin() {
528
870
 
529
871
  // Check if element is a text node
530
872
  const isTextNode = element.textContent && element.children.length === 0;
873
+ const textContent = isTextNode ? element.textContent.trim() : '';
874
+
875
+ // Check if element is an image node
876
+ const isImageNode = element.tagName === 'IMG';
877
+ const imageUrl = isImageNode ? element.src : '';
531
878
 
532
879
  const elementData = {
533
880
  tagName: element.tagName,
@@ -540,22 +887,28 @@ export function inspectorPlugin() {
540
887
  width: element.getBoundingClientRect().width,
541
888
  height: element.getBoundingClientRect().height
542
889
  },
543
- isTextNode: isTextNode
890
+ isTextNode: isTextNode,
891
+ textContent: textContent,
892
+ isImageNode: isImageNode,
893
+ imageUrl: imageUrl
544
894
  };
545
895
 
546
896
  // Send info to parent window
547
897
  if (window.parent !== window) {
548
- window.parent.postMessage({
898
+ // Use JSON.stringify to ensure all properties are serialized correctly
899
+ const message = JSON.stringify({
549
900
  type: 'INSPECTOR_ELEMENT_SELECTED',
550
901
  data: elementData
551
- }, '*');
902
+ });
903
+ window.parent.postMessage(message, '*');
552
904
  }
553
905
 
554
906
  // Log for debugging
555
907
  console.log('Element selected:', {
556
908
  element,
557
909
  componentInfo,
558
- isTextNode
910
+ isTextNode,
911
+ textContent
559
912
  });
560
913
 
561
914
  // Pause inspection and show control box
@@ -571,6 +924,8 @@ export function inspectorPlugin() {
571
924
  document.body.style.cursor = 'crosshair';
572
925
  document.addEventListener('mousemove', handleMouseMove, true);
573
926
  document.addEventListener('click', handleClick, true);
927
+ document.addEventListener('scroll', handleScroll, true);
928
+ window.addEventListener('resize', handleResize, true);
574
929
  } else {
575
930
  // Clean up everything
576
931
  clearHighlight();
@@ -578,6 +933,14 @@ export function inspectorPlugin() {
578
933
  document.body.style.cursor = '';
579
934
  document.removeEventListener('mousemove', handleMouseMove, true);
580
935
  document.removeEventListener('click', handleClick, true);
936
+ document.removeEventListener('scroll', handleScroll, true);
937
+ window.removeEventListener('resize', handleResize, true);
938
+
939
+ // Notify parent that inspector is closed
940
+ window.parent.postMessage({
941
+ type: 'INSPECTOR_CLOSED',
942
+ source: 'inspector'
943
+ }, '*');
581
944
  }
582
945
  }
583
946
 
@@ -596,10 +959,184 @@ export function inspectorPlugin() {
596
959
  theme = { ...theme, ...event.data.theme };
597
960
  }
598
961
  } else if (event.data.type === 'SHOW_CONTENT_INPUT') {
962
+ updateTextImmediately = event.data.updateImmediately || false;
599
963
  toggleContentInput(event.data.show);
964
+ } else if (event.data.type === 'SHOW_IMAGE_INPUT') {
965
+ updateImageImmediately = event.data.updateImmediately || false;
966
+ toggleImageInput(event.data.show);
967
+ } else if (event.data.type === 'SET_BADGE_VISIBLE') {
968
+ if (event.data.badgeText) {
969
+ labels.badgeText = event.data.badgeText;
970
+ }
971
+ setBadgeVisible(event.data.visible);
600
972
  }
601
973
  });
602
974
 
975
+ // Create Promake badge
976
+ function createPromakeBadge() {
977
+ if (promakeBadge) return;
978
+
979
+ promakeBadge = document.createElement('div');
980
+ promakeBadge.id = 'promake-badge';
981
+ promakeBadge.setAttribute('data-inspector-ignore', 'true');
982
+ promakeBadge.style.cssText = \`
983
+ position: fixed;
984
+ bottom: 20px;
985
+ right: 20px;
986
+ background: linear-gradient(135deg, \${theme.badgeGradientStart} 0%, \${theme.badgeGradientEnd} 100%);
987
+ color: \${theme.badgeTextColor};
988
+ padding: 10px 16px;
989
+ border-radius: 25px;
990
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
991
+ font-size: 13px;
992
+ font-weight: 600;
993
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
994
+ cursor: pointer;
995
+ z-index: 999999;
996
+ display: flex;
997
+ align-items: center;
998
+ gap: 8px;
999
+ transition: all 0.3s ease;
1000
+ text-decoration: none;
1001
+ opacity: 0;
1002
+ transform: translateY(10px);
1003
+ \`;
1004
+
1005
+ promakeBadge.innerHTML = \`
1006
+ <svg width="18" height="18" viewBox="0 0 380 380" fill="none" xmlns="http://www.w3.org/2000/svg">
1007
+ <path d="M251.207 64.1165C237.318 55.3491 219.31 53.3822 203.749 56.7106C188.186 60.0395 175.085 68.6608 170.995 81.0528L170.995 81.0537C167.196 92.5423 168.03 106.257 170.817 120.204C173.603 134.149 178.341 148.32 182.349 160.724L182.439 160.941L182.392 160.96L182.345 160.979C177.201 148.366 169.529 136.94 159.801 127.405C150.073 117.87 138.497 110.428 125.784 105.537L125.784 105.536L125.437 105.425C114.066 102.063 101.905 102.529 90.8242 106.749C79.7511 110.966 70.3677 118.699 64.1131 128.762L64.1203 128.77C55.3499 142.664 53.3822 160.676 56.71 176.238C60.0383 191.802 68.6596 204.902 81.0519 208.992C92.5485 212.789 106.795 212.017 121.273 209.292C135.749 206.567 150.449 201.891 162.852 197.883L162.853 197.884L162.856 197.883L162.89 197.976C150.273 203.12 136.95 210.452 127.415 220.177C117.879 229.903 110.435 241.479 105.542 254.19L105.43 254.54C102.068 265.911 102.533 278.071 106.753 289.153C110.841 299.888 118.234 309.034 127.851 315.282L128.788 315.878C142.683 324.648 160.692 326.615 176.253 323.286C191.816 319.957 204.916 311.336 209.011 298.946C212.811 287.456 210.342 273.716 205.916 259.738C203.703 252.75 201.002 245.705 198.354 238.855C195.705 232.005 193.108 225.35 191.105 219.144L191.191 219.097L191.192 219.098L191.193 219.098C191.193 219.099 191.194 219.101 191.195 219.102C191.198 219.105 191.2 219.11 191.205 219.115C191.214 219.127 191.229 219.143 191.247 219.166C191.284 219.212 191.338 219.28 191.41 219.369C191.553 219.546 191.766 219.808 192.039 220.145C192.585 220.818 193.377 221.79 194.357 222.987C196.317 225.381 199.031 228.671 202.05 232.26C208.088 239.439 215.34 247.808 220.204 252.576C229.929 262.112 241.502 269.559 254.212 274.458L254.211 274.457L254.544 274.574L254.543 274.574C265.915 277.93 278.075 277.463 289.156 273.245C299.891 269.158 309.041 261.77 315.295 252.157L315.891 251.22C324.659 237.331 326.624 219.325 323.295 203.765C319.966 188.203 311.344 175.104 298.955 171.008C287.458 167.212 273.211 167.95 258.732 170.641C244.254 173.332 229.55 177.973 217.141 181.979L217.107 181.885C229.723 176.739 243.054 169.544 252.586 159.819C262.121 150.092 269.563 138.516 274.455 125.804L274.567 125.453L274.568 125.453C277.921 114.082 277.451 101.925 273.232 90.8464C269.013 79.767 261.276 70.3771 251.208 64.117L251.207 64.1165ZM196.312 178.439C199.375 180.118 201.646 182.945 202.625 186.298C203.604 189.65 203.211 193.255 201.532 196.318C199.854 199.381 197.027 201.653 193.674 202.632C190.321 203.61 186.716 203.217 183.653 201.539C180.59 199.86 178.32 197.033 177.34 193.681C176.362 190.328 176.754 186.722 178.433 183.659C180.112 180.596 182.939 178.326 186.292 177.347C189.645 176.368 193.249 176.761 196.312 178.439Z" fill="url(#promake-gradient)" stroke="white" stroke-width="0.1"/>
1008
+ <path d="M248.582 92.4298C249.573 90.6498 251.841 89.9883 253.443 91.2462C254.876 92.3714 256.175 93.6632 257.313 95.0972C259.216 97.4972 260.628 100.249 261.468 103.194C262.308 106.14 262.56 109.222 262.209 112.265C261.999 114.083 261.576 115.866 260.952 117.578C260.255 119.491 257.979 120.126 256.199 119.136V119.136C254.419 118.146 253.826 115.902 254.381 113.943C254.615 113.118 254.783 112.275 254.881 111.419C255.121 109.339 254.949 107.231 254.375 105.217C253.801 103.203 252.835 101.322 251.534 99.6805C250.999 99.0059 250.411 98.3775 249.778 97.8004C248.272 96.4288 247.592 94.2098 248.582 92.4298V92.4298Z" fill="white"/>
1009
+ <path d="M215.025 79.7402C213.937 78.0186 214.443 75.7101 216.314 74.9064C217.988 74.1876 219.744 73.6656 221.548 73.3541C224.566 72.8328 227.658 72.9111 230.646 73.5845C233.634 74.258 236.46 75.5134 238.963 77.2791C240.459 78.3341 241.822 79.559 243.025 80.9264C244.371 82.4552 243.838 84.7576 242.116 85.846V85.846C240.395 86.9343 238.141 86.3802 236.687 84.9538C236.076 84.3537 235.415 83.8025 234.712 83.3062C233 82.0989 231.068 81.2405 229.024 80.78C226.981 80.3195 224.867 80.2659 222.803 80.6224C221.955 80.7689 221.122 80.9835 220.312 81.2632C218.387 81.9281 216.113 81.4619 215.025 79.7402V79.7402Z" fill="white"/>
1010
+ <defs>
1011
+ <radialGradient id="promake-gradient" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(158.467 247.082) rotate(-57.462) scale(150.285 150.298)">
1012
+ <stop stop-color="#4417DB"/>
1013
+ <stop offset="1" stop-color="#DA738C"/>
1014
+ </radialGradient>
1015
+ </defs>
1016
+ </svg>
1017
+ <span>\${labels.badgeText}</span>
1018
+ <button id="promake-badge-close" style="
1019
+ position: absolute;
1020
+ top: -6px;
1021
+ right: -6px;
1022
+ width: 20px;
1023
+ height: 20px;
1024
+ border-radius: 50%;
1025
+ background: rgba(0, 0, 0, 0.6);
1026
+ color: white;
1027
+ border: 2px solid white;
1028
+ display: flex;
1029
+ align-items: center;
1030
+ justify-content: center;
1031
+ cursor: pointer;
1032
+ font-size: 12px;
1033
+ font-weight: bold;
1034
+ opacity: 0;
1035
+ transition: all 0.2s ease;
1036
+ padding: 0;
1037
+ line-height: 1;
1038
+ ">×</button>
1039
+ \`;
1040
+
1041
+ // Get close button
1042
+ const closeButton = promakeBadge.querySelector('#promake-badge-close');
1043
+
1044
+ // Hover effect - show close button on hover
1045
+ promakeBadge.addEventListener('mouseenter', () => {
1046
+ promakeBadge.style.transform = 'translateY(-2px)';
1047
+ promakeBadge.style.boxShadow = '0 6px 20px rgba(102, 126, 234, 0.6)';
1048
+ if (closeButton) {
1049
+ closeButton.style.opacity = '1';
1050
+ }
1051
+ });
1052
+
1053
+ promakeBadge.addEventListener('mouseleave', () => {
1054
+ promakeBadge.style.transform = 'translateY(0)';
1055
+ promakeBadge.style.boxShadow = '0 4px 12px rgba(102, 126, 234, 0.4)';
1056
+ if (closeButton) {
1057
+ closeButton.style.opacity = '0';
1058
+ }
1059
+ });
1060
+
1061
+ // Close button click handler
1062
+ if (closeButton) {
1063
+ closeButton.addEventListener('click', (e) => {
1064
+ e.stopPropagation(); // Prevent badge click event
1065
+ setBadgeVisible(false);
1066
+ });
1067
+
1068
+ // Hover effect for close button
1069
+ closeButton.addEventListener('mouseenter', () => {
1070
+ closeButton.style.background = 'rgba(0, 0, 0, 0.8)';
1071
+ closeButton.style.transform = 'scale(1.1)';
1072
+ });
1073
+
1074
+ closeButton.addEventListener('mouseleave', () => {
1075
+ closeButton.style.background = 'rgba(0, 0, 0, 0.6)';
1076
+ closeButton.style.transform = 'scale(1)';
1077
+ });
1078
+ }
1079
+
1080
+ // Click to open Promake website (optional)
1081
+ promakeBadge.addEventListener('click', () => {
1082
+ window.open('https://promake.ai', '_blank');
1083
+ });
1084
+
1085
+ document.body.appendChild(promakeBadge);
1086
+
1087
+ // Animate in
1088
+ setTimeout(() => {
1089
+ promakeBadge.style.opacity = '1';
1090
+ promakeBadge.style.transform = 'translateY(0)';
1091
+ }, 100);
1092
+ }
1093
+
1094
+ // Update badge text
1095
+ function updateBadgeText() {
1096
+ if (promakeBadge) {
1097
+ const textSpan = promakeBadge.querySelector('span');
1098
+ if (textSpan) {
1099
+ textSpan.textContent = labels.badgeText;
1100
+ }
1101
+ }
1102
+ }
1103
+
1104
+ // Update badge colors
1105
+ function updateBadgeColors() {
1106
+ if (promakeBadge) {
1107
+ promakeBadge.style.background = \`linear-gradient(135deg, \${theme.badgeGradientStart} 0%, \${theme.badgeGradientEnd} 100%)\`;
1108
+ promakeBadge.style.color = theme.badgeTextColor;
1109
+ }
1110
+ }
1111
+
1112
+ // Set badge visibility
1113
+ function setBadgeVisible(visible) {
1114
+ if (visible) {
1115
+ if (!promakeBadge) {
1116
+ createPromakeBadge();
1117
+ } else {
1118
+ // Update text and colors if badge already exists
1119
+ updateBadgeText();
1120
+ updateBadgeColors();
1121
+ promakeBadge.style.display = 'flex';
1122
+ setTimeout(() => {
1123
+ promakeBadge.style.opacity = '1';
1124
+ promakeBadge.style.transform = 'translateY(0)';
1125
+ }, 50);
1126
+ }
1127
+ } else {
1128
+ if (promakeBadge) {
1129
+ promakeBadge.style.opacity = '0';
1130
+ promakeBadge.style.transform = 'translateY(10px)';
1131
+ setTimeout(() => {
1132
+ if (promakeBadge) {
1133
+ promakeBadge.style.display = 'none';
1134
+ }
1135
+ }, 300);
1136
+ }
1137
+ }
1138
+ }
1139
+
603
1140
  // Track URL changes
604
1141
  let lastUrl = location.href;
605
1142
 
@@ -609,7 +1146,7 @@ export function inspectorPlugin() {
609
1146
  lastUrl = newUrl;
610
1147
 
611
1148
  if (window.parent !== window) {
612
- window.parent.postMessage({
1149
+ const message = JSON.stringify({
613
1150
  type: 'URL_CHANGED',
614
1151
  data: {
615
1152
  url: newUrl,
@@ -617,7 +1154,8 @@ export function inspectorPlugin() {
617
1154
  search: location.search,
618
1155
  hash: location.hash
619
1156
  }
620
- }, '*');
1157
+ });
1158
+ window.parent.postMessage(message, '*');
621
1159
  }
622
1160
  }
623
1161
  }
@@ -650,10 +1188,11 @@ export function inspectorPlugin() {
650
1188
 
651
1189
  function sendError(errorData) {
652
1190
  if (window.parent !== window) {
653
- window.parent.postMessage({
1191
+ const message = JSON.stringify({
654
1192
  type: 'INSPECTOR_ERROR',
655
1193
  data: errorData
656
- }, '*');
1194
+ });
1195
+ window.parent.postMessage(message, '*');
657
1196
  }
658
1197
  // Use original console.error to avoid infinite loop
659
1198
  originalConsoleError.call(console, '🔍 Inspector Error:', errorData);
@@ -699,6 +1238,13 @@ export function inspectorPlugin() {
699
1238
  originalConsoleError.apply(console, args);
700
1239
  };
701
1240
 
1241
+ // Auto-show badge in preview.promake.ai environment (when not in iframe)
1242
+ if (window.parent === window && window.location.href.includes('preview.promake.ai')) {
1243
+ // Not in iframe and in preview environment - show badge automatically
1244
+ setBadgeVisible(true);
1245
+ console.log('🏷️ Badge auto-enabled for preview.promake.ai');
1246
+ }
1247
+
702
1248
  console.log('🔍 Inspector plugin loaded');
703
1249
  })();
704
1250
  `,