@promakeai/inspector 0.0.4 → 0.1.1

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)
@@ -299,6 +339,27 @@ export function inspectorPlugin() {
299
339
  textSubmit.addEventListener('click', (e) => {
300
340
  e.stopPropagation();
301
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
+
302
363
  if (window.parent !== window) {
303
364
  const message = JSON.stringify({
304
365
  type: 'INSPECTOR_TEXT_UPDATED',
@@ -347,31 +408,276 @@ export function inspectorPlugin() {
347
408
  }
348
409
  }
349
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
+
350
635
  // Adjust control box position based on content
351
636
  function adjustControlBoxPosition() {
352
637
  if (!controlBox || !selectedElement) return;
353
638
 
354
639
  const rect = selectedElement.getBoundingClientRect();
355
- const boxWidth = Math.max(320, Math.min(rect.width, 500));
356
- const centerLeft = rect.left + (rect.width / 2) - (boxWidth / 2);
640
+ const viewportWidth = window.innerWidth;
357
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
358
654
  const spaceBelow = viewportHeight - rect.bottom;
359
655
  const spaceAbove = rect.top;
360
656
 
361
- // Get actual box height
657
+ // Get actual box height and apply max height
362
658
  const boxHeight = controlBox.offsetHeight;
659
+ const maxBoxHeight = Math.min(400, viewportHeight - (padding * 4));
363
660
 
364
- // Show above if not enough space below
661
+ // Determine position with viewport bounds
365
662
  let topPosition;
366
- if (spaceBelow < boxHeight + 20 && spaceAbove > spaceBelow) {
367
- 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);
368
666
  controlBox.setAttribute('data-position', 'above');
369
667
  } else {
668
+ // Show below element
370
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
+ }
371
674
  controlBox.setAttribute('data-position', 'below');
372
675
  }
373
676
 
374
677
  controlBox.style.top = topPosition + 'px';
678
+ controlBox.style.left = centerLeft + 'px';
679
+ controlBox.style.width = boxWidth + 'px';
680
+ controlBox.style.maxHeight = maxBoxHeight + 'px';
375
681
  }
376
682
 
377
683
  // Pause inspection
@@ -518,6 +824,23 @@ export function inspectorPlugin() {
518
824
  }
519
825
  }
520
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
+
521
844
  // Handle click
522
845
  function handleClick(e) {
523
846
  if (!inspectMode) return;
@@ -549,6 +872,10 @@ export function inspectorPlugin() {
549
872
  const isTextNode = element.textContent && element.children.length === 0;
550
873
  const textContent = isTextNode ? element.textContent.trim() : '';
551
874
 
875
+ // Check if element is an image node
876
+ const isImageNode = element.tagName === 'IMG';
877
+ const imageUrl = isImageNode ? element.src : '';
878
+
552
879
  const elementData = {
553
880
  tagName: element.tagName,
554
881
  className: element.className,
@@ -561,7 +888,9 @@ export function inspectorPlugin() {
561
888
  height: element.getBoundingClientRect().height
562
889
  },
563
890
  isTextNode: isTextNode,
564
- textContent: textContent
891
+ textContent: textContent,
892
+ isImageNode: isImageNode,
893
+ imageUrl: imageUrl
565
894
  };
566
895
 
567
896
  // Send info to parent window
@@ -596,6 +925,7 @@ export function inspectorPlugin() {
596
925
  document.addEventListener('mousemove', handleMouseMove, true);
597
926
  document.addEventListener('click', handleClick, true);
598
927
  document.addEventListener('scroll', handleScroll, true);
928
+ window.addEventListener('resize', handleResize, true);
599
929
  } else {
600
930
  // Clean up everything
601
931
  clearHighlight();
@@ -604,6 +934,13 @@ export function inspectorPlugin() {
604
934
  document.removeEventListener('mousemove', handleMouseMove, true);
605
935
  document.removeEventListener('click', handleClick, true);
606
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
+ }, '*');
607
944
  }
608
945
  }
609
946
 
@@ -622,10 +959,184 @@ export function inspectorPlugin() {
622
959
  theme = { ...theme, ...event.data.theme };
623
960
  }
624
961
  } else if (event.data.type === 'SHOW_CONTENT_INPUT') {
962
+ updateTextImmediately = event.data.updateImmediately || false;
625
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);
626
972
  }
627
973
  });
628
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
+
629
1140
  // Track URL changes
630
1141
  let lastUrl = location.href;
631
1142
 
@@ -727,6 +1238,74 @@ export function inspectorPlugin() {
727
1238
  originalConsoleError.apply(console, args);
728
1239
  };
729
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
+
1248
+ // 🖼️ Auto cache-bust all images on page load
1249
+ // This ensures images are always fresh when page is refreshed/reloaded
1250
+ function cacheBustAllImages() {
1251
+ const timestamp = Date.now();
1252
+ let imageCount = 0;
1253
+
1254
+ // Cache-bust all <img> tags
1255
+ const images = document.querySelectorAll('img');
1256
+ images.forEach((img) => {
1257
+ if (img.src && !img.src.startsWith('data:')) {
1258
+ try {
1259
+ const url = new URL(img.src);
1260
+ // Remove old cache busting params
1261
+ url.searchParams.delete('_t');
1262
+ url.searchParams.delete('_cache_bust');
1263
+ url.searchParams.delete('v');
1264
+ // Add new timestamp
1265
+ url.searchParams.set('_t', timestamp.toString());
1266
+ img.src = url.toString();
1267
+ imageCount++;
1268
+ } catch (e) {
1269
+ // Ignore invalid URLs
1270
+ }
1271
+ }
1272
+ });
1273
+
1274
+ // Cache-bust background images
1275
+ const allElements = document.querySelectorAll('*');
1276
+ allElements.forEach((el) => {
1277
+ const bgImage = window.getComputedStyle(el).backgroundImage;
1278
+ if (bgImage && bgImage !== 'none' && bgImage.includes('url(')) {
1279
+ const urlMatch = bgImage.match(/url\(['"]?([^'"]+)['"]?\)/);
1280
+ if (urlMatch && urlMatch[1] && !urlMatch[1].startsWith('data:')) {
1281
+ try {
1282
+ const url = new URL(urlMatch[1], window.location.origin);
1283
+ url.searchParams.delete('_t');
1284
+ url.searchParams.delete('_cache_bust');
1285
+ url.searchParams.delete('v');
1286
+ url.searchParams.set('_t', timestamp.toString());
1287
+ (el as HTMLElement).style.backgroundImage = 'url("' + url.toString() + '")';
1288
+ imageCount++;
1289
+ } catch (e) {
1290
+ // Ignore invalid URLs
1291
+ }
1292
+ }
1293
+ }
1294
+ });
1295
+
1296
+ if (imageCount > 0) {
1297
+ console.log('🖼️ [INSPECTOR] Cache-busted ' + imageCount + ' images on page load');
1298
+ }
1299
+ }
1300
+
1301
+ // Run cache-bust after DOM is fully loaded
1302
+ if (document.readyState === 'loading') {
1303
+ document.addEventListener('DOMContentLoaded', cacheBustAllImages);
1304
+ } else {
1305
+ // DOM already loaded, run immediately
1306
+ cacheBustAllImages();
1307
+ }
1308
+
730
1309
  console.log('🔍 Inspector plugin loaded');
731
1310
  })();
732
1311
  `,