diff --git a/public/_kleap/kleap-component-selector-client.js b/public/_kleap/kleap-component-selector-client.js
new file mode 100644
index 0000000..2198c42
--- /dev/null
+++ b/public/_kleap/kleap-component-selector-client.js
@@ -0,0 +1,963 @@
+(() => {
+ const OVERLAY_ID = "__kleap_overlay__";
+ let overlay, label;
+
+ // The possible states are:
+ // { type: 'inactive' }
+ // { type: 'inspecting', element: ?HTMLElement }
+ // { type: 'selected', element: HTMLElement }
+ let state = { type: "inactive" };
+
+ /* ---------- helpers --------------------------------------------------- */
+ const css = (el, obj) => Object.assign(el.style, obj);
+
+ function getElementPath(element) {
+ const path = [];
+ let current = element;
+ let depth = 0;
+ const maxDepth = 5;
+
+ while (current && current !== document.body && depth < maxDepth) {
+ let identifier = current.tagName.toLowerCase();
+
+ if (current.id) {
+ identifier += `#${current.id}`;
+ } else if (current.className && typeof current.className === 'string') {
+ const firstClass = current.className.trim().split(' ').filter(c => c && !c.startsWith('_'))[0];
+ if (firstClass) {
+ identifier += `.${firstClass}`;
+ }
+ }
+
+ path.unshift(identifier);
+ current = current.parentElement;
+ depth++;
+ }
+
+ return path.join(' > ');
+ }
+
+ function isTextElement(el) {
+ // Check if element contains ANY text (even nested)
+ if (!el) return false;
+
+ // Get all text content including nested elements
+ const textContent = el.textContent || '';
+
+ // Check if it's a text-focused element
+ const textTags = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'P', 'SPAN', 'A', 'BUTTON', 'LI', 'TD', 'TH', 'LABEL', 'DIV'];
+ const hasText = textContent.trim().length > 0;
+ const isTextTag = textTags.includes(el.tagName);
+
+ // If element has text and is a common text element, it's editable
+ return hasText && (isTextTag || el.childNodes.length === 1);
+ }
+
+ function isImageElement(el) {
+ return el && el.tagName === 'IMG';
+ }
+
+ function enableImageEdit(img) {
+ // Get image position
+ const rect = img.getBoundingClientRect();
+
+ // Create context menu
+ const menu = document.createElement('div');
+ css(menu, {
+ position: 'fixed',
+ top: `${Math.min(rect.bottom + 8, window.innerHeight - 200)}px`,
+ left: `${Math.min(rect.left, window.innerWidth - 250)}px`,
+ background: 'white',
+ borderRadius: '12px',
+ boxShadow: '0 8px 32px rgba(0,0,0,0.15)',
+ zIndex: '2147483648',
+ padding: '8px',
+ minWidth: '240px',
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
+ animation: 'kleapMenuSlideIn 0.2s ease-out',
+ });
+
+ // Add animation
+ const style = document.createElement('style');
+ style.textContent = `
+ @keyframes kleapMenuSlideIn {
+ from {
+ opacity: 0;
+ transform: translateY(-10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+ }
+
+ .kleap-menu-item {
+ padding: 10px 12px;
+ border-radius: 8px;
+ cursor: pointer;
+ transition: background 0.15s;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ color: #333;
+ font-size: 14px;
+ }
+
+ .kleap-menu-item:hover {
+ background: #f5f5f5;
+ }
+
+ .kleap-menu-icon {
+ width: 20px;
+ height: 20px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+ `;
+ document.head.appendChild(style);
+
+ // Menu items with icons
+ const menuItems = [
+ {
+ icon: '📚',
+ label: 'Choose from Library',
+ action: () => showLibraryDialog(img)
+ },
+ {
+ icon: '🔗',
+ label: 'Change URL',
+ action: () => showUrlDialog(img)
+ },
+ {
+ icon: '📤',
+ label: 'Upload Image',
+ action: () => showUploadDialog(img)
+ },
+ {
+ icon: '✨',
+ label: 'Generate with AI',
+ action: () => showAIDialog(img)
+ }
+ ];
+
+ menuItems.forEach(item => {
+ const menuItem = document.createElement('div');
+ menuItem.className = 'kleap-menu-item';
+
+ const icon = document.createElement('span');
+ icon.className = 'kleap-menu-icon';
+ icon.textContent = item.icon;
+
+ const label = document.createElement('span');
+ label.textContent = item.label;
+
+ menuItem.appendChild(icon);
+ menuItem.appendChild(label);
+
+ menuItem.onclick = () => {
+ menu.remove();
+ style.remove();
+ item.action();
+ };
+
+ menu.appendChild(menuItem);
+ });
+
+ document.body.appendChild(menu);
+
+ // Close menu on click outside
+ const closeMenu = (e) => {
+ if (!menu.contains(e.target) && e.target !== img) {
+ menu.remove();
+ style.remove();
+ document.removeEventListener('click', closeMenu);
+ }
+ };
+
+ setTimeout(() => {
+ document.addEventListener('click', closeMenu);
+ }, 100);
+ }
+
+ function showLibraryDialog(img) {
+ // Send message to parent to open Asset Manager
+ window.parent.postMessage({
+ type: 'kleap-image-library-select',
+ id: img.dataset.kleapId || `img-${Math.random().toString(36).substr(2, 9)}`,
+ oldSrc: img.src,
+ alt: img.alt || '',
+ }, '*');
+ }
+
+ function showUrlDialog(img) {
+ const dialog = createDialog('Change Image URL');
+
+ const input = document.createElement('input');
+ input.type = 'text';
+ input.value = img.src;
+ input.placeholder = 'Enter image URL...';
+ css(input, {
+ width: '100%',
+ padding: '10px 12px',
+ border: '1px solid #ddd',
+ borderRadius: '8px',
+ fontSize: '14px',
+ marginBottom: '16px',
+ boxSizing: 'border-box',
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
+ });
+
+ dialog.content.appendChild(input);
+
+ dialog.onSave = () => {
+ const newSrc = input.value.trim();
+ if (newSrc && newSrc !== img.src) {
+ updateImage(img, newSrc);
+ }
+ };
+
+ input.focus();
+ input.select();
+ }
+
+ function showUploadDialog(img) {
+ // Open Asset Manager directly - it has full upload functionality
+ window.parent.postMessage({
+ type: 'kleap-image-library-select',
+ id: img.dataset.kleapId || `img-${Math.random().toString(36).substr(2, 9)}`,
+ oldSrc: img.src,
+ alt: img.alt || '',
+ }, '*');
+ }
+
+ function showAIDialog(img) {
+ const dialog = createDialog('Generate with AI');
+
+ // Model selection
+ const modelLabel = document.createElement('div');
+ css(modelLabel, {
+ fontSize: '12px',
+ color: '#666',
+ marginBottom: '8px',
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
+ });
+ modelLabel.textContent = 'Choose Model:';
+
+ const modelContainer = document.createElement('div');
+ css(modelContainer, {
+ display: 'grid',
+ gridTemplateColumns: 'repeat(2, 1fr)',
+ gap: '8px',
+ marginBottom: '16px',
+ });
+
+ const models = [
+ { id: 'flux-pro', name: '🚀 Flux Pro', desc: 'Fast & High quality' },
+ { id: 'flux-dev', name: '⚡ Flux Dev', desc: 'Good balance' },
+ { id: 'sdxl', name: '🎨 Stable Diffusion', desc: 'Classic & reliable' },
+ { id: 'playground-v2', name: '✨ Playground v2', desc: 'Aesthetic focus' },
+ { id: 'kandinsky', name: '🖼️ Kandinsky', desc: 'Artistic style' },
+ { id: 'dalle-3', name: '🤖 DALL-E 3', desc: 'OpenAI (if available)' }
+ ];
+
+ let selectedModel = 'flux-pro';
+ const modelButtons = [];
+
+ models.forEach(model => {
+ const button = document.createElement('div');
+ css(button, {
+ padding: '8px',
+ borderRadius: '8px',
+ border: '2px solid #ddd',
+ cursor: 'pointer',
+ transition: 'all 0.2s',
+ textAlign: 'center',
+ background: model.id === selectedModel ? '#ff0055' : 'white',
+ color: model.id === selectedModel ? 'white' : '#333',
+ });
+
+ button.innerHTML = `
+
${model.name.split(' ')[0]}
+ ${model.name.substring(model.name.indexOf(' ') + 1)}
+ ${model.desc}
+ `;
+
+ button.onclick = () => {
+ selectedModel = model.id;
+ // Update button styles
+ modelButtons.forEach(btn => {
+ css(btn, {
+ background: btn === button ? '#ff0055' : 'white',
+ color: btn === button ? 'white' : '#333',
+ border: btn === button ? '2px solid #ff0055' : '2px solid #ddd',
+ });
+ });
+ };
+
+ modelButtons.push(button);
+ modelContainer.appendChild(button);
+ });
+
+ // Prompt textarea
+ const promptLabel = document.createElement('div');
+ css(promptLabel, {
+ fontSize: '12px',
+ color: '#666',
+ marginBottom: '8px',
+ marginTop: '16px',
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
+ });
+ promptLabel.textContent = 'Describe your image:';
+
+ const textarea = document.createElement('textarea');
+ textarea.placeholder = 'A beautiful landscape with mountains and a sunset...';
+ css(textarea, {
+ width: '100%',
+ minHeight: '80px',
+ padding: '10px 12px',
+ border: '1px solid #ddd',
+ borderRadius: '8px',
+ fontSize: '14px',
+ marginBottom: '12px',
+ resize: 'vertical',
+ boxSizing: 'border-box',
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
+ });
+
+ // Style presets
+ const styleLabel = document.createElement('div');
+ css(styleLabel, {
+ fontSize: '12px',
+ color: '#666',
+ marginBottom: '8px',
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
+ });
+ styleLabel.textContent = 'Style presets (optional):';
+
+ const styleContainer = document.createElement('div');
+ css(styleContainer, {
+ display: 'flex',
+ flexWrap: 'wrap',
+ gap: '6px',
+ marginBottom: '16px',
+ });
+
+ const styles = ['Photorealistic', 'Artistic', 'Anime', '3D Render', 'Watercolor', 'Oil Painting', 'Minimalist', 'Vintage'];
+
+ styles.forEach(style => {
+ const chip = document.createElement('div');
+ css(chip, {
+ padding: '4px 10px',
+ borderRadius: '12px',
+ border: '1px solid #ddd',
+ fontSize: '12px',
+ cursor: 'pointer',
+ transition: 'all 0.15s',
+ background: 'white',
+ color: '#666',
+ });
+ chip.textContent = style;
+
+ chip.onclick = () => {
+ const isSelected = chip.style.background === 'rgb(255, 0, 85)';
+ css(chip, {
+ background: isSelected ? 'white' : '#ff0055',
+ color: isSelected ? '#666' : 'white',
+ border: isSelected ? '1px solid #ddd' : '1px solid #ff0055',
+ });
+
+ // Add/remove from prompt
+ if (!isSelected) {
+ if (!textarea.value.includes(style.toLowerCase())) {
+ textarea.value = textarea.value.trim() + (textarea.value ? ', ' : '') + style.toLowerCase() + ' style';
+ }
+ } else {
+ textarea.value = textarea.value.replace(new RegExp(`,?\\s*${style.toLowerCase()}\\s*style`, 'gi'), '');
+ }
+ };
+
+ styleContainer.appendChild(chip);
+ });
+
+ // Append all elements
+ dialog.content.appendChild(modelLabel);
+ dialog.content.appendChild(modelContainer);
+ dialog.content.appendChild(promptLabel);
+ dialog.content.appendChild(textarea);
+ dialog.content.appendChild(styleLabel);
+ dialog.content.appendChild(styleContainer);
+
+ dialog.saveBtn.textContent = '🎨 Generate';
+
+ dialog.onSave = () => {
+ const prompt = textarea.value.trim();
+ if (prompt) {
+ // Send to parent for AI generation with selected model
+ window.parent.postMessage({
+ type: 'kleap-image-ai-generate',
+ id: img.dataset.kleapId || `img-${Math.random().toString(36).substr(2, 9)}`,
+ prompt: prompt,
+ model: selectedModel,
+ oldSrc: img.src,
+ }, '*');
+
+ // Show generating state
+ dialog.content.innerHTML = `
+
+
✨
+
Generating with ${models.find(m => m.id === selectedModel)?.name || 'AI'}...
+
"${prompt.substring(0, 50)}${prompt.length > 50 ? '...' : ''}"
+
This may take 10-30 seconds
+
+
+ `;
+ dialog.saveBtn.style.display = 'none';
+ }
+ };
+
+ textarea.focus();
+ }
+
+ function createDialog(title) {
+ const overlay = document.createElement('div');
+ css(overlay, {
+ position: 'fixed',
+ top: '0',
+ left: '0',
+ right: '0',
+ bottom: '0',
+ background: 'rgba(0,0,0,0.5)',
+ zIndex: '2147483647',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ });
+
+ const dialog = document.createElement('div');
+ css(dialog, {
+ background: 'white',
+ borderRadius: '16px',
+ boxShadow: '0 20px 60px rgba(0,0,0,0.3)',
+ minWidth: '400px',
+ maxWidth: '500px',
+ animation: 'kleapDialogIn 0.2s ease-out',
+ });
+
+ const header = document.createElement('div');
+ css(header, {
+ padding: '20px 24px',
+ borderBottom: '1px solid #eee',
+ fontSize: '16px',
+ fontWeight: '600',
+ color: '#333',
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
+ });
+ header.textContent = title;
+
+ const content = document.createElement('div');
+ css(content, {
+ padding: '20px 24px',
+ });
+
+ const footer = document.createElement('div');
+ css(footer, {
+ padding: '16px 24px',
+ borderTop: '1px solid #eee',
+ display: 'flex',
+ gap: '8px',
+ justifyContent: 'flex-end',
+ });
+
+ const cancelBtn = document.createElement('button');
+ cancelBtn.textContent = 'Cancel';
+ css(cancelBtn, {
+ padding: '8px 16px',
+ border: '1px solid #ddd',
+ borderRadius: '8px',
+ background: 'white',
+ color: '#666',
+ fontSize: '14px',
+ cursor: 'pointer',
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
+ });
+
+ const saveBtn = document.createElement('button');
+ saveBtn.textContent = 'Apply';
+ css(saveBtn, {
+ padding: '8px 20px',
+ border: 'none',
+ borderRadius: '8px',
+ background: '#ff0055',
+ color: 'white',
+ fontSize: '14px',
+ cursor: 'pointer',
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
+ });
+
+ footer.appendChild(cancelBtn);
+ footer.appendChild(saveBtn);
+
+ dialog.appendChild(header);
+ dialog.appendChild(content);
+ dialog.appendChild(footer);
+ overlay.appendChild(dialog);
+ document.body.appendChild(overlay);
+
+ const close = () => overlay.remove();
+
+ cancelBtn.onclick = close;
+ overlay.onclick = (e) => {
+ if (e.target === overlay) close();
+ };
+
+ const result = {
+ content,
+ saveBtn,
+ close,
+ onSave: null,
+ };
+
+ saveBtn.onclick = () => {
+ if (result.onSave) result.onSave();
+ close();
+ };
+
+ // Handle keyboard
+ document.addEventListener('keydown', function handleKey(e) {
+ if (e.key === 'Escape') {
+ close();
+ document.removeEventListener('keydown', handleKey);
+ }
+ });
+
+ return result;
+ }
+
+ function updateImage(img, newSrc) {
+ const originalSrc = img.src;
+ img.src = newSrc;
+
+ // Send the change to parent
+ window.parent.postMessage({
+ type: 'kleap-image-edited',
+ id: img.dataset.kleapId || `img-${Math.random().toString(36).substr(2, 9)}`,
+ oldSrc: originalSrc,
+ newSrc: newSrc,
+ alt: img.alt || '',
+ }, '*');
+ }
+
+ function enableInlineEdit(el) {
+ // Store original state
+ const originalText = el.textContent;
+ const originalHTML = el.innerHTML;
+
+ // Store ALL original styles
+ const originalStyles = {
+ outline: el.style.outline,
+ outlineOffset: el.style.outlineOffset,
+ boxShadow: el.style.boxShadow,
+ cursor: el.style.cursor,
+ contentEditable: el.contentEditable,
+ // Don't modify background, border, or any other styles!
+ };
+
+ // Make element editable
+ el.contentEditable = 'true';
+ el.focus();
+
+ // Add ONLY non-intrusive visual feedback
+ css(el, {
+ outline: '2px solid #ff0055',
+ outlineOffset: '3px',
+ cursor: 'text',
+ boxShadow: '0 0 0 4px rgba(255, 0, 85, 0.1), 0 4px 12px rgba(0, 0, 0, 0.08)',
+ // DO NOT change background, border, borderRadius, or any other properties!
+ });
+
+ // Select all text
+ const range = document.createRange();
+ range.selectNodeContents(el);
+ const selection = window.getSelection();
+ selection.removeAllRanges();
+ selection.addRange(range);
+
+ // Handle blur to save changes
+ const handleBlur = () => {
+ const newText = el.textContent;
+
+ if (newText !== originalText) {
+ // Show saving status on the border
+ css(el, {
+ outline: '2px solid #FFA500', // Orange for saving
+ boxShadow: '0 0 0 4px rgba(255, 165, 0, 0.2), 0 4px 12px rgba(0, 0, 0, 0.08)',
+ });
+
+ // Send the change to parent
+ // Generate ID and name if not present
+ const id = el.dataset.kleapId || `element-${Math.random().toString(36).substr(2, 9)}`;
+ let name = el.dataset.kleapName || el.tagName.toLowerCase();
+
+ if (!el.dataset.kleapName && el.className && typeof el.className === 'string') {
+ const classes = el.className.trim().split(' ').filter(c => c);
+ if (classes.length > 0) {
+ name = `${el.tagName.toLowerCase()}.${classes[0]}`;
+ }
+ }
+
+ window.parent.postMessage({
+ type: 'kleap-text-edited',
+ id: id,
+ name: name,
+ path: el.dataset.kleapPath || getElementPath(el),
+ oldText: originalText,
+ newText: newText,
+ tagName: el.tagName.toLowerCase()
+ }, '*');
+
+ // Show success after a moment
+ setTimeout(() => {
+ css(el, {
+ outline: '2px solid #00C851', // Green for success
+ boxShadow: '0 0 0 4px rgba(0, 200, 81, 0.2), 0 4px 12px rgba(0, 0, 0, 0.08)',
+ });
+
+ // Remove all styles after showing success
+ setTimeout(() => {
+ el.contentEditable = originalStyles.contentEditable || 'false';
+ css(el, {
+ outline: originalStyles.outline || '',
+ outlineOffset: originalStyles.outlineOffset || '',
+ boxShadow: originalStyles.boxShadow || '',
+ cursor: originalStyles.cursor || ''
+ });
+ }, 1000);
+ }, 500);
+ } else {
+ // Restore original HTML if no changes
+ el.innerHTML = originalHTML;
+
+ // Restore styles immediately if no changes
+ el.contentEditable = originalStyles.contentEditable || 'false';
+ css(el, {
+ outline: originalStyles.outline || '',
+ outlineOffset: originalStyles.outlineOffset || '',
+ boxShadow: originalStyles.boxShadow || '',
+ cursor: originalStyles.cursor || ''
+ });
+ }
+
+ el.removeEventListener('blur', handleBlur);
+ el.removeEventListener('keydown', handleKeydown);
+ };
+
+ // Handle escape key to cancel
+ const handleKeydown = (e) => {
+ if (e.key === 'Escape') {
+ e.preventDefault();
+ el.innerHTML = originalHTML;
+ el.blur();
+ }
+ };
+
+ el.addEventListener('blur', handleBlur);
+ el.addEventListener('keydown', handleKeydown);
+ }
+
+ function makeOverlay() {
+ overlay = document.createElement("div");
+ overlay.id = OVERLAY_ID;
+ css(overlay, {
+ position: "absolute",
+ border: "2px solid #ff0055",
+ background: "rgba(255,0,85,.05)",
+ pointerEvents: "none",
+ zIndex: "2147483647", // max
+ borderRadius: "8px",
+ boxShadow: "0 0 0 1px rgba(255,0,85,0.2), 0 4px 12px rgba(0,0,0,0.08)",
+ transition: "all 0.2s cubic-bezier(0.4, 0, 0.2, 1)",
+ });
+
+ label = document.createElement("div");
+ css(label, {
+ position: "absolute",
+ left: "50%",
+ top: "100%",
+ transform: "translateX(-50%) translateY(8px)",
+ background: "linear-gradient(135deg, #1a1a1a 0%, #0a0a0a 100%)",
+ color: "#fff",
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
+ fontSize: "13px",
+ fontWeight: "500",
+ lineHeight: "1.4",
+ padding: "0",
+ borderRadius: "12px",
+ boxShadow: "0 8px 32px rgba(0,0,0,0.24), 0 2px 8px rgba(0,0,0,0.16), inset 0 1px 0 rgba(255,255,255,0.06)",
+ border: "1px solid rgba(255,255,255,0.08)",
+ overflow: "hidden",
+ minWidth: "180px",
+ animation: "kleapFadeIn 0.2s ease-out",
+ pointerEvents: "auto", // Enable clicks on the label
+ });
+ overlay.appendChild(label);
+ document.body.appendChild(overlay);
+
+ // Add animation keyframes
+ const style = document.createElement("style");
+ style.textContent = `
+ @keyframes kleapFadeIn {
+ from {
+ opacity: 0;
+ transform: translateX(-50%) translateY(4px);
+ }
+ to {
+ opacity: 1;
+ transform: translateX(-50%) translateY(8px);
+ }
+ }
+
+ #${OVERLAY_ID} .kleap-option {
+ transition: all 0.15s ease;
+ }
+
+ #${OVERLAY_ID} .kleap-option:hover {
+ background: rgba(255,255,255,0.06);
+ }
+
+ #${OVERLAY_ID} .kleap-option:active {
+ transform: scale(0.98);
+ }
+ `;
+ document.head.appendChild(style);
+ }
+
+ function updateOverlay(el) {
+ if (!overlay) makeOverlay();
+
+ const rect = el.getBoundingClientRect();
+ css(overlay, {
+ top: `${rect.top + window.scrollY}px`,
+ left: `${rect.left + window.scrollX}px`,
+ width: `${rect.width}px`,
+ height: `${rect.height}px`,
+ display: "block",
+ });
+
+ // Clear previous contents
+ while (label.firstChild) {
+ label.removeChild(label.firstChild);
+ }
+
+ // Always show minimal info when hovering
+ const info = document.createElement("div");
+ css(info, {
+ padding: "8px 12px",
+ fontSize: "12px",
+ opacity: "0.9",
+ });
+
+ // Get a descriptive name for the element
+ let name = el.dataset.kleapName || el.tagName.toLowerCase();
+
+ // Try to get a better description
+ if (!el.dataset.kleapName) {
+ const tag = el.tagName.toLowerCase();
+
+ // Add class names if available
+ if (el.className && typeof el.className === 'string') {
+ const classes = el.className.trim().split(' ').filter(c => c && !c.startsWith('_'));
+ if (classes.length > 0) {
+ // Filter out utility classes
+ const meaningfulClass = classes.find(c =>
+ !c.match(/^(flex|grid|block|inline|absolute|relative|fixed|sticky|w-|h-|p-|m-|text-|bg-|border-|rounded-|shadow-|opacity-|z-)/)
+ );
+ name = meaningfulClass ? `${tag}.${meaningfulClass}` : `${tag}.${classes[0]}`;
+ }
+ } else if (el.id) {
+ name = `${tag}#${el.id}`;
+ } else if (el.getAttribute('aria-label')) {
+ name = `${tag}[${el.getAttribute('aria-label')}]`;
+ } else if (el.getAttribute('role')) {
+ name = `${tag}[role=${el.getAttribute('role')}]`;
+ }
+ }
+
+ let actionHint = "";
+ if (isTextElement(el)) {
+ actionHint = " • Click to edit text";
+ } else if (isImageElement(el)) {
+ actionHint = " • Click to change image";
+ }
+ info.textContent = name + actionHint;
+ label.appendChild(info);
+ }
+
+ /* ---------- event handlers -------------------------------------------- */
+ function onMouseMove(e) {
+ if (state.type !== "inspecting") return;
+
+ let el = e.target;
+
+ // Don't require data-kleap-id - any element can be selected
+ // Skip only truly non-selectable elements
+ const nonSelectableTags = ['SCRIPT', 'STYLE', 'META', 'LINK', 'HTML', 'BODY'];
+ while (el && nonSelectableTags.includes(el.tagName)) {
+ el = el.parentElement;
+ }
+
+ if (state.element === el) return;
+ state.element = el;
+
+ if (el) {
+ updateOverlay(el);
+ } else {
+ if (overlay) overlay.style.display = "none";
+ }
+ }
+
+ function onClick(e) {
+ if (state.type !== "inspecting" || !state.element) return;
+ e.preventDefault();
+ e.stopPropagation();
+
+ const el = state.element;
+
+ // If it's a text element, enable inline edit directly
+ if (isTextElement(el)) {
+ enableInlineEdit(el);
+ deactivate();
+ } else if (isImageElement(el)) {
+ // If it's an image, show image edit dialog
+ enableImageEdit(el);
+ deactivate();
+ } else {
+ // For non-text elements, send to parent for AI editing
+ // Generate better ID and name
+ let id = el.dataset.kleapId;
+ let name = el.dataset.kleapName;
+ let path = el.dataset.kleapPath;
+
+ // Generate ID if not present
+ if (!id) {
+ // Try to create a meaningful ID from element properties
+ const tag = el.tagName.toLowerCase();
+ const uniqueId = Math.random().toString(36).substr(2, 9);
+
+ if (el.id) {
+ id = `${tag}#${el.id}`;
+ } else if (el.className && typeof el.className === 'string') {
+ const firstClass = el.className.trim().split(' ')[0];
+ id = `${tag}.${firstClass}-${uniqueId}`;
+ } else {
+ id = `${tag}-${uniqueId}`;
+ }
+ }
+
+ // Generate name if not present
+ if (!name) {
+ const tag = el.tagName.toLowerCase();
+ if (el.className && typeof el.className === 'string') {
+ const classes = el.className.trim().split(' ').filter(c => c && !c.startsWith('_'));
+ const meaningfulClass = classes.find(c =>
+ !c.match(/^(flex|grid|block|inline|absolute|relative|fixed|sticky|w-|h-|p-|m-|text-|bg-|border-|rounded-|shadow-|opacity-|z-)/)
+ );
+ name = meaningfulClass ? `${tag}.${meaningfulClass}` : `${tag}.${classes[0] || ''}`;
+ } else {
+ name = tag;
+ }
+ }
+
+ // Generate path if not present
+ if (!path) {
+ path = getElementPath(el);
+ }
+
+ window.parent.postMessage(
+ {
+ type: "kleap-component-selected",
+ id: id,
+ name: name,
+ path: path,
+ tagName: el.tagName.toLowerCase(),
+ className: el.className || '',
+ textContent: el.textContent ? el.textContent.substring(0, 100) : '',
+ hasChildren: el.children.length > 0,
+ rect: {
+ width: el.offsetWidth,
+ height: el.offsetHeight
+ }
+ },
+ "*",
+ );
+ deactivate();
+ }
+ }
+
+ /* ---------- activation / deactivation --------------------------------- */
+ function activate() {
+ if (state.type === "inactive") {
+ window.addEventListener("mousemove", onMouseMove, true);
+ window.addEventListener("click", onClick, true);
+ }
+ state = { type: "inspecting", element: null };
+ if (overlay) {
+ overlay.style.display = "none";
+ }
+ }
+
+ function deactivate() {
+ if (state.type === "inactive") return;
+
+ window.removeEventListener("mousemove", onMouseMove, true);
+ window.removeEventListener("click", onClick, true);
+ if (overlay) {
+ overlay.remove();
+ overlay = null;
+ label = null;
+ }
+ state = { type: "inactive" };
+ }
+
+ /* ---------- message bridge -------------------------------------------- */
+ window.addEventListener("message", (e) => {
+ if (e.source !== window.parent) return;
+ if (e.data.type === "activate-kleap-component-selector") activate();
+ if (e.data.type === "deactivate-kleap-component-selector") deactivate();
+ });
+
+ function initializeComponentSelector() {
+ if (!document.body) {
+ console.error(
+ "Kleap component selector initialization failed: document.body not found.",
+ );
+ return;
+ }
+ setTimeout(() => {
+ if (document.body.querySelector("[data-kleap-id]")) {
+ window.parent.postMessage(
+ {
+ type: "kleap-component-selector-initialized",
+ },
+ "*",
+ );
+ console.debug("Kleap component selector initialized");
+ } else {
+ console.warn(
+ "Kleap component selector not initialized because no DOM elements were tagged",
+ );
+ }
+ }, 0);
+ }
+
+ if (document.readyState === "loading") {
+ document.addEventListener("DOMContentLoaded", initializeComponentSelector);
+ } else {
+ initializeComponentSelector();
+ }
+})();