diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 91321cf..291afbb 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,36 +1,1421 @@ import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; +import { Halant } from "next/font/google"; +import { Inter } from "next/font/google"; +import { Manrope } from "next/font/google"; import "./globals.css"; -import { ServiceWrapper } from "@/providers/themeProvider/ServiceWrapper"; -import { Tag } from "@/components/tag/Tag"; +import { ServiceWrapper } from "@/components/ServiceWrapper"; +import Tag from "@/tag/Tag"; -const geist = Geist({ - variable: "--font-geist-sans", subsets: ["latin"], +const halant = Halant({ + variable: "--font-halant", subsets: ["latin"], + weight: ["300", "400", "500", "600", "700"], }); -const geist_mono = Geist_Mono({ - variable: "--font-geist-mono", subsets: ["latin"], +const inter = Inter({ + variable: "--font-inter", subsets: ["latin"], +}); + +const manrope = Manrope({ + variable: "--font-manrope", subsets: ["latin"], }); export const metadata: Metadata = { - title: "Portfolio | Creative Design & UX Expertise", description: "Explore innovative design solutions and digital experiences crafted with strategic thinking and creative excellence."}; + title: "Portfolio — Creative Design Excellence", description: "Innovative digital design solutions showcasing brand strategy, UX/UI design, and product design expertise. Explore featured projects and services.", keywords: "portfolio, graphic design, UX design, brand identity, digital design, creative services", openGraph: { + title: "Portfolio — Creative Design Excellence", description: "Discover innovative design solutions and creative work. Brand strategy, UX/UI, and digital product design.", siteName: "Portfolio", type: "website", images: [ + { + url: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AhRowzw9k0ZSJ87n7KX34EwoE1/a-stunning-creative-portfolio-showcasing-1773043068077-12221410.png", alt: "Portfolio showcase"}, + ], + }, + twitter: { + card: "summary_large_image", title: "Portfolio — Creative Design Excellence", description: "Innovative digital design solutions and creative work", images: [ + "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AhRowzw9k0ZSJ87n7KX34EwoE1/a-stunning-creative-portfolio-showcasing-1773043068077-12221410.png"], + }, + robots: { + index: true, + follow: true, + }, +}; export default function RootLayout({ children, -}: { +}: Readonly<{ children: React.ReactNode; -}) { +}>) { return ( - - + + {children} - + + dangerouslySetInnerHTML={{ + __html: ` +(function() { + if (window.self === window.top) return; + + if (window.__webildEditorInitialized) return; + window.__webildEditorInitialized = true; + + let isActive = false; + let hoveredElement = null; + let selectedElement = null; + let originalContent = null; + let isEditing = false; + let elementTypeLabel = null; + let hoverOverlay = null; + let scrollTimeout = null; + let isScrolling = false; + + const invalidElements = ['html', 'body', 'script', 'style', 'meta', 'link', 'head', 'noscript', 'title']; + const hoverClass = 'webild-hover'; + const selectedClass = 'webild-selected'; + + const style = document.createElement('style'); + style.id = 'webild-inspector-styles'; + style.textContent = '' + + '.webild-hover {' + + ' outline: 2px dashed #4d96ff80 !important;' + + ' border-radius: 0 !important;' + + ' outline-offset: 2px !important;' + + ' cursor: pointer !important;' + + ' transition: outline 0.15s ease !important;' + + ' background-color: #4d96ff05 !important;' + + '}' + + '.webild-selected {' + + ' outline: 2px solid #4d96ff !important;' + + ' outline-offset: 2px !important;' + + ' transition: outline 0.15s ease !important;' + + ' background-color: #4d96ff05 !important;' + + ' border-radius: 0 !important;' + + '}' + + '[contenteditable="true"].webild-selected {' + + ' outline: 2px solid #4d96ff !important;' + + ' background-color: #4d96ff05 !important;' + + '}' + + 'img.webild-hover,' + + 'img.webild-selected,' + + 'video.webild-hover,' + + 'video.webild-selected {' + + ' outline-offset: 2px !important;' + + '}' + + '.webild-element-type-label {' + + ' position: fixed !important;' + + ' z-index: 999999 !important;' + + ' background: #4d96ff !important;' + + ' color: white !important;' + + ' padding: 4px 8px !important;' + + ' font-size: 11px !important;' + + ' font-weight: 600 !important;' + + ' font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;' + + ' pointer-events: none !important;' + + ' white-space: nowrap !important;' + + ' box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15) !important;' + + ' letter-spacing: 0.3px !important;' + + ' border: 1px solid #4d96ff20 !important;' + + '}' + + '.webild-element-type-label.label-top {' + + ' border-radius: 6px 6px 0 0 !important;' + + '}' + + '.webild-element-type-label.label-bottom {' + + ' border-radius: 0 0 6px 6px !important;' + + '}' + + '.webild-hover-overlay {' + + ' position: fixed !important;' + + ' background-color: #4d96ff15 !important;' + + ' pointer-events: none !important;' + + ' z-index: 999998 !important;' + + ' transition: all 0.15s ease !important;' + + '}'; + document.head.appendChild(style); + + const getUniqueSelector = (element, assignId = false) => { + if (element.dataset && element.dataset.webildSelector) { + return element.dataset.webildSelector; + } + + const existingId = element.getAttribute('data-webild-id'); + if (existingId) { + return '[data-webild-id="' + existingId + '"]'; + } + + if (assignId) { + const uniqueId = 'webild-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9); + element.setAttribute('data-webild-id', uniqueId); + return '[data-webild-id="' + uniqueId + '"]'; + } + + return null; + }; + + const getSectionId = (element) => { + let current = element; + while (current && current !== document.body) { + const sectionId = current.getAttribute('data-section'); + if (sectionId) { + return sectionId; + } + current = current.parentElement; + } + return 'hero'; + }; + + const getElementType = (element) => { + const tagName = element.tagName.toLowerCase(); + const computedStyle = window.getComputedStyle(element); + + if (tagName === 'img') { + return 'Image'; + } + + if (tagName === 'video') { + return 'Video'; + } + + const backgroundImage = computedStyle.backgroundImage; + if (backgroundImage && backgroundImage !== 'none') { + const urlMatch = backgroundImage.match(/url(['"]?([^'")]+)['"]?)/); + if (urlMatch && urlMatch[1] && !urlMatch[1].includes('gradient')) { + const area = element.offsetWidth * element.offsetHeight; + const hasReasonableSize = area > 1000; + const hasFewChildren = element.children.length <= 2; + + if (hasReasonableSize && hasFewChildren) { + return 'Image'; + } + } + } + + if (tagName === 'button') return 'Button'; + if (tagName === 'a' && element.getAttribute('href')) return 'Button'; + if (element.getAttribute('role') === 'button') return 'Button'; + + const buttonClasses = ['btn', 'button', 'cta', 'action-button']; + const hasButtonClass = buttonClasses.some(cls => + element.classList.contains(cls) || element.classList.contains(\`btn-\${cls}\`) + ); + + if (hasButtonClass && element.textContent && element.textContent.trim().length > 0) { + return 'Button'; + } + + const textTags = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'span', 'label', 'li']; + if (textTags.includes(tagName)) { + return 'Text'; + } + + if (tagName === 'div') { + const hasDirectText = Array.from(element.childNodes).some(node => + node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim().length > 0 + ); + + if (hasDirectText && !element.querySelector('div, section, article, main, header, footer')) { + return 'Text'; + } + + return 'Div'; + } + + if (tagName === 'article') { + return 'Article'; + } + + if (tagName === 'a' && !element.getAttribute('href')) { + return 'Text'; + } + + return 'Section'; + }; + + const extractOriginalUrl = (url) => { + if (!url) return url; + + if (url.includes('/_next/')) { + try { + const urlObj = new URL(url); + const originalPath = urlObj.searchParams.get('url'); + if (originalPath) { + return originalPath; + } + } catch (e) { + return url; + } + } + + if (url.includes('.webild.io/')) { + try { + const urlObj = new URL(url); + return urlObj.pathname; + } catch (e) { + return url; + } + } + + return url; + }; + + const getMediaTypeFromUrl = (url) => { + const videoExts = ['.mp4', '.webm', '.ogg', '.mov', '.avi', '.mkv', '.m4v', '.wmv']; + const imageExts = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.bmp', '.ico', '.tiff', '.avif']; + try { + const pathname = new URL(url).pathname.toLowerCase(); + if (videoExts.some(function(ext) { return pathname.endsWith(ext); })) return 'video'; + if (imageExts.some(function(ext) { return pathname.endsWith(ext); })) return 'image'; + } catch(e) {} + return 'unknown'; + }; + + const swapMediaElement = (oldEl, newTag, newSrc) => { + const newEl = document.createElement(newTag); + Array.from(oldEl.attributes).forEach(function(attr) { + if (attr.name !== 'src' && attr.name !== 'alt' && attr.name !== 'srcset' && attr.name !== 'autoplay' && attr.name !== 'loop' && attr.name !== 'muted' && attr.name !== 'playsinline') { + try { newEl.setAttribute(attr.name, attr.value); } catch(e) {} + } + }); + newEl.style.cssText = oldEl.style.cssText; + if (newTag === 'video') { + newEl.setAttribute('autoplay', ''); + newEl.setAttribute('loop', ''); + newEl.setAttribute('muted', ''); + newEl.setAttribute('playsinline', ''); + } + newEl.src = newSrc; + if (oldEl.parentNode) { + oldEl.parentNode.replaceChild(newEl, oldEl); + } + return newEl; + }; + + const getElementInfo = (element, assignId = false) => { + const rect = element.getBoundingClientRect(); + const tagName = element.tagName.toLowerCase(); + const selector = getUniqueSelector(element, assignId); + const sectionId = getSectionId(element); + + let className = undefined; + try { + if (element.className) { + if (typeof element.className === 'string') { + className = element.className; + } else if (element.className.baseVal !== undefined) { + className = element.className.baseVal; + } + } + } catch (e) {} + + const info = { + tagName: tagName, + id: element.id || undefined, + className: className, + selector: selector, + elementType: null, + sectionId: sectionId, + boundingBox: { + x: rect.left, + y: rect.top, + width: rect.width, + height: rect.height + } + }; + + if (tagName === 'img') { + const originalSrc = extractOriginalUrl(element.src); + info.imageData = { + src: originalSrc, + alt: element.alt || undefined, + naturalWidth: element.naturalWidth, + naturalHeight: element.naturalHeight, + isBackground: false + }; + } + + if (tagName === 'video') { + const rawSrc = element.src || element.currentSrc || (element.querySelector('source') && element.querySelector('source').src) || ''; + const resolvedSrc = extractOriginalUrl(rawSrc); + info.imageData = { + src: resolvedSrc, + alt: element.getAttribute('aria-label') || undefined, + isBackground: false, + isVideo: true + }; + } + + const computedStyle = window.getComputedStyle(element); + const backgroundImage = computedStyle.backgroundImage; + if (backgroundImage && backgroundImage !== 'none') { + const urlMatch = backgroundImage.match(/url(['"]?([^'")]+)['"]?)/); + if (urlMatch) { + const originalBgSrc = extractOriginalUrl(urlMatch[1]); + if (tagName !== 'img') { + info.imageData = { + src: originalBgSrc, + isBackground: true + }; + } else { + if (!info.imageData) info.imageData = {}; + info.imageData.backgroundImageSrc = originalBgSrc; + } + } + } + + const elementType = getElementType(element); + info.elementType = elementType; + + if (elementType === 'Button') { + const buttonText = element.textContent?.trim() || element.value || element.getAttribute('aria-label') || ''; + const buttonHref = element.getAttribute('href') || + element.getAttribute('data-href') || + element.getAttribute('onclick') || + element.dataset?.link || + undefined; + + info.buttonData = { + text: buttonText, + href: buttonHref + }; + } + + if (elementType === 'Text') { + info.textContent = element.textContent || ''; + } + + return info; + }; + + const isValidElement = (element) => { + if (!isActive) return false; + const tagName = element.tagName?.toLowerCase(); + if (invalidElements.includes(tagName)) return false; + const isImage = tagName === 'img'; + const isVideo = tagName === 'video'; + if (isImage || isVideo) return true; + const hasInnerHTML = element.innerHTML && element.innerHTML.trim().length > 0; + const hasTextContent = element.textContent && element.textContent.trim().length > 0; + const hasChildren = element.children && element.children.length > 0; + if (!hasInnerHTML && !hasTextContent && !hasChildren) { + return false; + } + const hasBackgroundImage = window.getComputedStyle(element).backgroundImage !== 'none'; + if (hasBackgroundImage && !hasChildren && !hasTextContent) { + return false; + } + + return true; + }; + + const getMostSpecificElement = (x, y) => { + const elements = document.elementsFromPoint(x, y); + const validElements = elements.filter(el => + isValidElement(el) && + !el.classList.contains('webild-hover-overlay') && + !el.classList.contains('webild-element-type-label') + ); + + if (validElements.length === 0) return null; + const scoredElements = validElements.map(element => { + let score = 0; + const rect = element.getBoundingClientRect(); + const tagName = element.tagName.toLowerCase(); + const computedStyle = window.getComputedStyle(element); + let depth = 0; + let current = element; + while (current && current !== document.body) { + depth++; + current = current.parentElement; + } + score += depth * 2; + const hasDirectText = Array.from(element.childNodes).some(node => + node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim().length > 0 + ); + + const hasImages = element.tagName === 'IMG' || element.tagName === 'VIDEO' || computedStyle.backgroundImage !== 'none' || element.querySelector('img') || element.querySelector('video'); + const isInteractive = ['BUTTON', 'A', 'INPUT', 'SELECT', 'TEXTAREA'].includes(element.tagName); + const hasFewChildren = element.children.length <= 3; + const area = rect.width * rect.height; + const viewportArea = window.innerWidth * window.innerHeight; + const isSmallElement = area < viewportArea * 0.1; + if (hasDirectText) score += 20; + if (hasImages) score += 15; + if (isInteractive) score += 25; + if (hasFewChildren) score += 10; + if (isSmallElement) score += 5; + if (area > viewportArea * 0.3) score -= 30; + if (element.hasAttribute('data-section')) score -= 15; + if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tagName)) score += 20; + if (['p', 'span', 'label'].includes(tagName)) score += 15; + if (tagName === 'div' && !hasDirectText && element.children.length > 2) score -= 10; + + return { element, score }; + }); + scoredElements.sort((a, b) => b.score - a.score); + return scoredElements[0]?.element || validElements[0]; + }; + + const isTextElement = (element) => { + const elementType = getElementType(element); + return elementType === 'Text'; + }; + + const isButtonElement = (element) => { + const elementType = getElementType(element); + return elementType === 'Button'; + }; + + const updateButtonText = (element, newText) => { + const textNodes = []; + const walker = document.createTreeWalker( + element, + NodeFilter.SHOW_TEXT, + null + ); + + let node; + while (node = walker.nextNode()) { + if (node.textContent && node.textContent.trim()) { + textNodes.push(node); + } + } + + if (textNodes.length > 0) { + textNodes[0].textContent = newText; + for (let i = 1; i < textNodes.length; i++) { + textNodes[i].textContent = ''; + } + } else { + element.textContent = newText; + } + }; + + const makeEditable = (element, clickEvent) => { + if (!isTextElement(element)) return; + + originalContent = element.textContent; + element.contentEditable = 'true'; + + if (!element.dataset.webildOriginalWhiteSpace) { + const computedStyle = window.getComputedStyle(element); + element.dataset.webildOriginalWhiteSpace = computedStyle.whiteSpace; + element.dataset.webildOriginalWordWrap = computedStyle.wordWrap; + element.dataset.webildOriginalOverflowWrap = computedStyle.overflowWrap; + element.dataset.webildOriginalOverflow = computedStyle.overflow; + } + + element.style.whiteSpace = 'pre-wrap'; + element.style.wordWrap = 'break-word'; + element.style.overflowWrap = 'break-word'; + element.style.overflow = 'visible'; + + element.focus(); + isEditing = true; + + window.parent.postMessage({ + type: 'webild-text-editing-started', + data: { selector: getElementInfo(element).selector } + }, '*'); + + const handleBeforeInput = (e) => { + // Prevent deletion if it would leave the element empty + const currentText = element.textContent || ''; + const inputType = e.inputType; + + // Check if this is a delete operation that would leave the element empty + if ((inputType === 'deleteContentBackward' || inputType === 'deleteContentForward' || inputType === 'deleteByCut') && currentText.length <= 1) { + e.preventDefault(); + element.textContent = ' '; + // Move cursor to the beginning + const range = document.createRange(); + const sel = window.getSelection(); + range.setStart(element.firstChild || element, 0); + range.collapse(true); + sel.removeAllRanges(); + sel.addRange(range); + } + }; + + const handleInput = () => { + const elementInfo = getElementInfo(element); + let currentText = element.textContent; + + // Ensure there's always at least a space to keep the element editable + if (currentText === '' || currentText === null || currentText.length === 0) { + element.textContent = ' '; + currentText = ' '; + // Move cursor to the beginning + try { + const range = document.createRange(); + const sel = window.getSelection(); + range.setStart(element.firstChild || element, 0); + range.collapse(true); + sel.removeAllRanges(); + sel.addRange(range); + } catch (e) { + // Ignore cursor positioning errors + } + } + + window.parent.postMessage({ + type: 'webild-element-changed', + data: { + type: 'updateText', + selector: elementInfo.selector, + oldValue: originalContent, + newValue: currentText, + elementType: elementInfo.elementType, + sectionId: elementInfo.sectionId, + timestamp: Date.now() + } + }, '*'); + + if (currentText !== originalContent) { + window.parent.postMessage({ + type: 'webild-text-changed', + data: { + selector: elementInfo.selector, + hasChanges: true + } + }, '*'); + } + }; + + element.addEventListener('beforeinput', handleBeforeInput); + element.addEventListener('input', handleInput); + element.dataset.inputHandler = 'true'; + element.dataset.beforeInputHandler = 'true'; + + if (clickEvent && element.childNodes.length > 0) { + try { + let range = null; + + if (document.caretRangeFromPoint) { + range = document.caretRangeFromPoint(clickEvent.clientX, clickEvent.clientY); + } else if (document.caretPositionFromPoint) { + const position = document.caretPositionFromPoint(clickEvent.clientX, clickEvent.clientY); + if (position) { + range = document.createRange(); + range.setStart(position.offsetNode, position.offset); + range.collapse(true); + } + } + + if (range) { + const selection = window.getSelection(); + selection.removeAllRanges(); + selection.addRange(range); + } + } catch (e) { + console.warn('[Webild] Could not set caret position:', e); + } + } + }; + + const makeUneditable = (element, save = false) => { + if (!element || element.contentEditable !== 'true') return; + + element.contentEditable = 'false'; + isEditing = false; + + if (element.dataset.webildOriginalWhiteSpace) { + element.style.whiteSpace = element.dataset.webildOriginalWhiteSpace === 'normal' ? '' : element.dataset.webildOriginalWhiteSpace; + delete element.dataset.webildOriginalWhiteSpace; + } + if (element.dataset.webildOriginalWordWrap) { + element.style.wordWrap = element.dataset.webildOriginalWordWrap === 'normal' ? '' : element.dataset.webildOriginalWordWrap; + delete element.dataset.webildOriginalWordWrap; + } + if (element.dataset.webildOriginalOverflowWrap) { + element.style.overflowWrap = element.dataset.webildOriginalOverflowWrap === 'normal' ? '' : element.dataset.webildOriginalOverflowWrap; + delete element.dataset.webildOriginalOverflowWrap; + } + if (element.dataset.webildOriginalOverflow) { + element.style.overflow = element.dataset.webildOriginalOverflow === 'visible' ? '' : element.dataset.webildOriginalOverflow; + delete element.dataset.webildOriginalOverflow; + } + + if (element.dataset.beforeInputHandler === 'true') { + element.removeEventListener('beforeinput', () => {}); + delete element.dataset.beforeInputHandler; + } + + if (element.dataset.inputHandler === 'true') { + element.removeEventListener('input', () => {}); + delete element.dataset.inputHandler; + } + + window.parent.postMessage({ + type: 'webild-text-editing-ended', + data: { selector: getElementInfo(element).selector } + }, '*'); + + if (save && originalContent !== element.textContent) { + const elementInfo = getElementInfo(element); + let finalText = element.textContent; + + // Trim the final text and convert space-only to empty string for saving + if (finalText === ' ' || finalText.trim() === '') { + finalText = ''; + // Update the actual element text to empty for display + element.textContent = ''; + } + + const change = { + type: 'updateText', + selector: elementInfo.selector, + oldValue: originalContent, + newValue: finalText, + elementType: elementInfo.elementType, + sectionId: elementInfo.sectionId, + timestamp: Date.now() + }; + + saveChangeToStorage(change); + + window.parent.postMessage({ + type: 'webild-element-changed', + data: change + }, '*'); + } else if (!save && originalContent !== null) { + element.textContent = originalContent; + } + + originalContent = null; + }; + + const createHoverOverlay = (element) => { + const rect = element.getBoundingClientRect(); + const overlay = document.createElement('div'); + overlay.className = 'webild-hover-overlay'; + overlay.style.cssText = \` + position: fixed !important; + top: \${rect.top - 2}px !important; + left: \${rect.left - 2}px !important; + width: \${rect.width + 4}px !important; + height: \${rect.height + 4}px !important; + background-color: rgba(90, 113, 230, 0.15) !important; + pointer-events: none !important; + z-index: 999998 !important; + transition: all 0.15s ease !important; + \`; + document.body.appendChild(overlay); + return overlay; + }; + + const removeHoverOverlay = () => { + if (hoverOverlay) { + hoverOverlay.remove(); + hoverOverlay = null; + } + }; + + const showElementTypeLabel = (element, elementType) => { + if (!elementType) return; + + removeElementTypeLabel(); + + const rect = element.getBoundingClientRect(); + elementTypeLabel = document.createElement('div'); + elementTypeLabel.className = 'webild-element-type-label'; + const ariaLabel = element.getAttribute('aria-label'); + let labelText; + + if (elementType === 'Div') { + labelText = 'Div'; + } else if (elementType === 'Article') { + labelText = 'Article'; + } else if (elementType === 'Section') { + labelText = ariaLabel || 'Section'; + } else { + labelText = elementType; + } + + elementTypeLabel.textContent = labelText; + document.body.appendChild(elementTypeLabel); + const labelRect = elementTypeLabel.getBoundingClientRect(); + let labelTop = rect.top - labelRect.height - 2; + let labelLeft = rect.left - 3; + let isLabelOnTop = true; + if (labelTop < 0) { + labelTop = rect.bottom + 1; + isLabelOnTop = false; + } + if (labelTop + labelRect.height > window.innerHeight) { + labelTop = rect.bottom - labelRect.height; + isLabelOnTop = false; + if (labelTop < 0) { + labelTop = rect.top; + isLabelOnTop = true; + } + } + if (labelLeft + labelRect.width > window.innerWidth) { + labelLeft = window.innerWidth - labelRect.width; + } + if (labelLeft < 0) { + labelLeft = 0; + } + if (isLabelOnTop) { + elementTypeLabel.classList.add('label-top'); + } else { + elementTypeLabel.classList.add('label-bottom'); + } + + elementTypeLabel.style.cssText = \` + left: \${labelLeft}px !important; + top: \${labelTop}px !important; + transform: none !important; + \`; + }; + + const removeElementTypeLabel = () => { + if (elementTypeLabel) { + elementTypeLabel.remove(); + elementTypeLabel = null; + } + }; + + const handleMouseOver = (e) => { + if (!isActive) return; + + lastMouseX = e.clientX; + lastMouseY = e.clientY; + + const target = getMostSpecificElement(e.clientX, e.clientY) || e.target; + + if (!isValidElement(target) || target === hoveredElement || target === selectedElement) { + return; + } + + if (hoveredElement && hoveredElement !== selectedElement) { + hoveredElement.classList.remove(hoverClass); + if (hoveredElement.dataset.webildOriginalPosition) { + hoveredElement.style.position = hoveredElement.dataset.webildOriginalPosition === 'none' ? '' : hoveredElement.dataset.webildOriginalPosition; + delete hoveredElement.dataset.webildOriginalPosition; + } + removeHoverOverlay(); + removeElementTypeLabel(); + } + + hoveredElement = target; + + const computedStyle = window.getComputedStyle(target); + const currentPosition = computedStyle.position; + + if (currentPosition === 'static' || currentPosition === '') { + hoveredElement.dataset.webildOriginalPosition = currentPosition || 'none'; + hoveredElement.style.position = 'relative'; + } + + hoveredElement.classList.add(hoverClass); + + if ((!selectedElement || selectedElement !== target) && !isScrolling) { + hoverOverlay = createHoverOverlay(target); + } + + const elementType = getElementType(target); + showElementTypeLabel(target, elementType); + + window.parent.postMessage({ + type: 'webild-element-hover', + data: getElementInfo(target, false) + }, '*'); + }; + + const handleMouseOut = (e) => { + if (!isActive) return; + + if (hoveredElement && hoveredElement !== selectedElement) { + hoveredElement.classList.remove(hoverClass); + + if (hoveredElement.dataset.webildOriginalPosition) { + hoveredElement.style.position = hoveredElement.dataset.webildOriginalPosition === 'none' ? '' : hoveredElement.dataset.webildOriginalPosition; + delete hoveredElement.dataset.webildOriginalPosition; + } + + removeHoverOverlay(); + removeElementTypeLabel(); + + hoveredElement = null; + + window.parent.postMessage({ + type: 'webild-element-hover', + data: null + }, '*'); + } + }; + + const handleClick = (e) => { + if (!isActive) return; + + if (isEditing) { + e.stopPropagation(); + return; + } + + e.preventDefault(); + e.stopPropagation(); + + const target = getMostSpecificElement(e.clientX, e.clientY) || e.target; + if (!isValidElement(target)) return; + + if (selectedElement && selectedElement !== target) { + makeUneditable(selectedElement, false); + selectedElement.classList.remove(selectedClass); + selectedElement.classList.remove(hoverClass); + + if (selectedElement.dataset.webildOriginalPosition) { + selectedElement.style.position = selectedElement.dataset.webildOriginalPosition === 'none' ? '' : selectedElement.dataset.webildOriginalPosition; + delete selectedElement.dataset.webildOriginalPosition; + } + + removeHoverOverlay(); + removeElementTypeLabel(); + } + + if (selectedElement === target) { + if (target.dataset.webildOriginalPosition) { + target.style.position = target.dataset.webildOriginalPosition === 'none' ? '' : target.dataset.webildOriginalPosition; + delete target.dataset.webildOriginalPosition; + } + + removeHoverOverlay(); + removeElementTypeLabel(); + + selectedElement = null; + window.parent.postMessage({ + type: 'webild-element-selected', + data: null + }, '*'); + return; + } + + selectedElement = target; + selectedElement.classList.add(selectedClass); + + removeHoverOverlay(); + removeElementTypeLabel(); + + if (hoveredElement === target) { + hoveredElement.classList.remove(hoverClass); + hoveredElement = null; + } + + const elementInfo = getElementInfo(target, true); + selectedElement.dataset.webildSelector = elementInfo.selector; + showElementTypeLabel(target, elementInfo.elementType); + + window.parent.postMessage({ + type: 'webild-element-selected', + data: elementInfo + }, '*'); + + if (isTextElement(target)) { + setTimeout(() => makeEditable(target, e), 50); + } + }; + + const handleKeyDown = (e) => { + if (!isActive) return; + if (!isEditing || !selectedElement) return; + + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + makeUneditable(selectedElement, true); + } else if (e.key === 'Escape') { + e.preventDefault(); + makeUneditable(selectedElement, false); + } + }; + + const handleBlur = (e) => { + if (!isActive) return; + if (isEditing && selectedElement && e.target === selectedElement) { + makeUneditable(selectedElement, true); + } + }; + + let lastMouseX = 0; + let lastMouseY = 0; + + const handleScroll = () => { + if (!isActive) return; + + if (isEditing) return; + + if (selectedElement) { + makeUneditable(selectedElement, false); + selectedElement.classList.remove(selectedClass); + if (selectedElement.dataset.webildOriginalPosition) { + selectedElement.style.position = selectedElement.dataset.webildOriginalPosition === 'none' ? '' : selectedElement.dataset.webildOriginalPosition; + delete selectedElement.dataset.webildOriginalPosition; + } + selectedElement = null; + + window.parent.postMessage({ + type: 'webild-element-selected', + data: null + }, '*'); + } + + if (hoveredElement) { + hoveredElement.classList.remove(hoverClass); + if (hoveredElement.dataset.webildOriginalPosition) { + hoveredElement.style.position = hoveredElement.dataset.webildOriginalPosition === 'none' ? '' : hoveredElement.dataset.webildOriginalPosition; + delete hoveredElement.dataset.webildOriginalPosition; + } + hoveredElement = null; + + window.parent.postMessage({ + type: 'webild-element-hover', + data: null + }, '*'); + } + + removeHoverOverlay(); + removeElementTypeLabel(); + + isScrolling = true; + + if (scrollTimeout) { + clearTimeout(scrollTimeout); + } + + scrollTimeout = setTimeout(() => { + isScrolling = false; + + if (lastMouseX > 0 && lastMouseY > 0) { + const target = getMostSpecificElement(lastMouseX, lastMouseY); + if (target && isValidElement(target) && target !== selectedElement) { + hoveredElement = target; + + const computedStyle = window.getComputedStyle(target); + const currentPosition = computedStyle.position; + + if (currentPosition === 'static' || currentPosition === '') { + hoveredElement.dataset.webildOriginalPosition = currentPosition || 'none'; + hoveredElement.style.position = 'relative'; + } + + hoveredElement.classList.add(hoverClass); + hoverOverlay = createHoverOverlay(target); + + const elementType = getElementType(target); + showElementTypeLabel(target, elementType); + + window.parent.postMessage({ + type: 'webild-element-hover', + data: getElementInfo(target, false) + }, '*'); + } + } + }, 150); + + window.parent.postMessage({ + type: 'webild-iframe-scroll' + }, '*'); + }; + + const getStorageKey = () => { + const url = new URL(window.location.href); + const pathParts = url.pathname.split('/').filter(Boolean); + return \`webild-changes-\${pathParts.join('-')}\`; + }; + + const saveChangeToStorage = (change) => { + try { + const storageKey = getStorageKey(); + const existingChanges = JSON.parse(localStorage.getItem(storageKey) || '[]'); + + const filteredChanges = existingChanges.filter(c => { + return !(c.oldValue === change.oldValue && c.sectionId === change.sectionId); + }); + filteredChanges.push(change); + + localStorage.setItem(storageKey, JSON.stringify(filteredChanges)); + + window.parent.postMessage({ + type: 'webild-change-saved-locally', + data: { change, allChanges: filteredChanges } + }, '*'); + } catch (error) { + console.error('Failed to save change to localStorage:', error); + } + }; + + const clearLocalChanges = () => { + try { + const storageKey = getStorageKey(); + localStorage.removeItem(storageKey); + window.parent.postMessage({ + type: 'webild-local-changes-cleared', + data: {} + }, '*'); + } catch (error) { + console.error('Failed to clear local changes:', error); + } + }; + + const handleMessage = (e) => { + if (!e.data || !e.data.type) return; + + if (e.data.type === 'webild-activate-editor') { + if (!isActive) { + isActive = true; + window.parent.postMessage({ type: 'webild-editor-activated' }, '*'); + } + return; + } + + if (e.data.type === 'webild-deactivate-editor') { + if (isActive) { + isActive = false; + + if (selectedElement) { + makeUneditable(selectedElement, false); + selectedElement.classList.remove(selectedClass); + selectedElement = null; + } + if (hoveredElement) { + hoveredElement.classList.remove(hoverClass); + hoveredElement = null; + } + + removeHoverOverlay(); + removeElementTypeLabel(); + window.parent.postMessage({ type: 'webild-editor-deactivated' }, '*'); + } + return; + } + + if (e.data.type === 'webild-clear-local-changes') { + clearLocalChanges(); + return; + } + + if (e.data.type === 'webild-cancel-changes') { + try { + const storageKey = getStorageKey(); + const savedChanges = localStorage.getItem(storageKey); + if (savedChanges) { + const changes = JSON.parse(savedChanges); + changes.forEach(change => { + try { + const element = document.querySelector(change.selector); + if (!element) return; + + if (change.type === 'updateText') { + if (isTextElement(element)) { + element.textContent = change.oldValue; + } + } else if (change.type === 'updateButton') { + if (isButtonElement(element)) { + updateButtonText(element, change.oldValue); + } + } else if (change.type === 'replaceImage') { + const revertTag = element.tagName.toLowerCase(); + const isBackground = revertTag !== 'img' && revertTag !== 'video'; + if (isBackground) { + element.style.backgroundImage = change.oldValue ? 'url(' + change.oldValue + ')' : ''; + } else { + const oldMediaType = getMediaTypeFromUrl(change.oldValue); + if (revertTag === 'video' && oldMediaType === 'image') { + swapMediaElement(element, 'img', change.oldValue); + } else if (revertTag === 'img' && oldMediaType === 'video') { + swapMediaElement(element, 'video', change.oldValue); + } else if (revertTag === 'video') { + element.src = change.oldValue; + element.load(); + } else { + element.src = change.oldValue; + } + } + } + } catch (err) { + console.warn('[Webild] Failed to revert change:', err); + } + }); + } + clearLocalChanges(); + } catch (error) { + console.error('[Webild] Failed to cancel changes:', error); + } + return; + } + + if (e.data.type === 'webild-update-text') { + const { selector, newValue, oldValue, sectionId } = e.data.data; + try { + let element = null; + + if (selectedElement && isTextElement(selectedElement)) { + element = selectedElement; + } + else if (selector) { + try { + element = document.querySelector(selector); + } catch (err) { + console.warn('[Webild] Invalid selector:', selector); + } + } + + if (!element && sectionId) { + const sectionElement = document.querySelector('[data-section="' + sectionId + '"]'); + if (sectionElement) { + const textElements = sectionElement.querySelectorAll('h1, h2, h3, h4, h5, h6, p, span, a, button, div'); + for (let i = 0; i < textElements.length; i++) { + const el = textElements[i]; + if (isTextElement(el) && el.textContent.trim() === (oldValue || '').trim()) { + element = el; + const newSelector = getUniqueSelector(element, true); + if (newSelector) { + element.dataset.webildSelector = newSelector; + } + break; + } + } + } + } + + if (element && isTextElement(element)) { + element.textContent = newValue; + const finalSelector = element.dataset.webildSelector || getUniqueSelector(element, true); + + window.parent.postMessage({ + type: 'webild-text-update-success', + data: { + selector: finalSelector, + newValue: newValue + } + }, '*'); + } else { + window.parent.postMessage({ + type: 'webild-text-update-failed', + data: { selector, newValue } + }, '*'); + } + } catch (error) { + window.parent.postMessage({ + type: 'webild-text-update-failed', + data: { selector, newValue, error: error.message } + }, '*'); + } + return; + } + + if (e.data.type === 'webild-update-button') { + const { selector, text, href } = e.data.data; + try { + const element = document.querySelector(selector); + if (element && isButtonElement(element)) { + if (text !== undefined) { + updateButtonText(element, text); + } + if (href !== undefined) { + if (element.tagName.toLowerCase() === 'a') { + element.href = href; + } else { + element.setAttribute('data-href', href); + } + } + } + } catch (error) { + console.error('[Webild] Invalid selector for button update:', selector, error); + } + return; + } + + if (!isActive) return; + + if (e.data.type === 'webild-replace-image') { + const { selector, newSrc, isBackground, allowMediaTypeSwap } = e.data.data; + let element = null; + + try { + element = document.querySelector(selector); + } catch { + window.parent.postMessage({ + type: 'webild-image-replacement-error', + data: { selector, message: 'Invalid selector: ' + error.message, success: false } + }, '*'); + return; + } + + if (!element) { + window.parent.postMessage({ + type: 'webild-image-replacement-error', + data: { selector, message: 'Element not found', success: false } + }, '*'); + return; + } + + try { + let replaced = false; + let oldValue = ''; + + if (isBackground) { + oldValue = window.getComputedStyle(element).backgroundImage; + element.style.backgroundImage = \`url('\${newSrc}')\`; + replaced = true; + } else if (element.tagName.toLowerCase() === 'img') { + oldValue = element.src; + const newMediaType = getMediaTypeFromUrl(newSrc); + if (newMediaType === 'video' && allowMediaTypeSwap) { + const swapped = swapMediaElement(element, 'video', newSrc); + if (selectedElement === element) selectedElement = swapped; + element = swapped; + } else { + element.src = newSrc; + } + replaced = true; + } else if (element.tagName.toLowerCase() === 'video') { + oldValue = element.src || element.currentSrc || ''; + const newMediaType = getMediaTypeFromUrl(newSrc); + const sources = element.querySelectorAll('source'); + if (newMediaType === 'image' && allowMediaTypeSwap) { + const swapped = swapMediaElement(element, 'img', newSrc); + if (selectedElement === element) selectedElement = swapped; + element = swapped; + } else { + if (sources.length > 0) { + sources.forEach(function(source) { source.src = newSrc; }); + element.load(); + } else { + element.src = newSrc; + element.load(); + } + } + replaced = true; + } else { + const hasBackgroundImage = window.getComputedStyle(element).backgroundImage !== 'none'; + if (hasBackgroundImage) { + oldValue = window.getComputedStyle(element).backgroundImage; + element.style.backgroundImage = \`url('\${newSrc}')\`; + replaced = true; + } + } + + if (replaced) { + const elementInfo = getElementInfo(element); + + let cleanOldValue = oldValue; + if (oldValue.includes('url(')) { + const urlMatch = oldValue.match(/url(['"]?([^'")]+)['"]?)/); + if (urlMatch) { + cleanOldValue = urlMatch[1]; + } + } + + cleanOldValue = extractOriginalUrl(cleanOldValue); + + const change = { + type: 'replaceImage', + selector: selector, + oldValue: cleanOldValue, + newValue: newSrc, + elementType: elementInfo.elementType, + sectionId: elementInfo.sectionId, + timestamp: Date.now() + }; + + saveChangeToStorage(change); + + window.parent.postMessage({ + type: 'webild-element-changed', + data: change + }, '*'); + + window.parent.postMessage({ + type: 'webild-image-replaced', + data: { selector, newSrc, success: true } + }, '*'); + } else { + window.parent.postMessage({ + type: 'webild-image-replacement-error', + data: { selector, message: 'Could not determine how to replace image', success: false } + }, '*'); + } + } catch (error) { + window.parent.postMessage({ + type: 'webild-image-replacement-error', + data: { selector, message: error.message || 'Failed to replace image', success: false } + }, '*'); + } + } + }; + + document.addEventListener('mouseover', handleMouseOver, true); + document.addEventListener('mouseout', handleMouseOut, true); + document.addEventListener('click', handleClick, true); + document.addEventListener('keydown', handleKeyDown, true); + document.addEventListener('blur', handleBlur, true); + window.addEventListener('scroll', handleScroll, true); + window.addEventListener('message', handleMessage, true); + + let lastPathname = window.location.pathname; + + const notifyPageChange = () => { + window.parent.postMessage({ + type: 'webild-page-changed', + data: { pathname: window.location.pathname } + }, '*'); + }; + + window.addEventListener('popstate', () => { + if (lastPathname !== window.location.pathname) { + lastPathname = window.location.pathname; + notifyPageChange(); + } + }, true); + + const urlCheckInterval = setInterval(() => { + if (lastPathname !== window.location.pathname) { + lastPathname = window.location.pathname; + notifyPageChange(); + } + }, 500); + + notifyPageChange(); + + window.webildCleanup = () => { + isActive = false; + + if (selectedElement) { + makeUneditable(selectedElement, false); + } + + removeHoverOverlay(); + removeElementTypeLabel(); + + if (urlCheckInterval) { + clearInterval(urlCheckInterval); + } + + document.removeEventListener('mouseover', handleMouseOver, true); + document.removeEventListener('mouseout', handleMouseOut, true); + document.removeEventListener('click', handleClick, true); + document.removeEventListener('keydown', handleKeyDown, true); + document.removeEventListener('blur', handleBlur, true); + window.removeEventListener('scroll', handleScroll, true); + window.removeEventListener('message', handleMessage, true); + + document.querySelectorAll('.' + hoverClass).forEach(el => { + el.classList.remove(hoverClass); + }); + document.querySelectorAll('.' + selectedClass).forEach(el => { + el.classList.remove(selectedClass); + }); + + const styleEl = document.getElementById('webild-inspector-styles'); + if (styleEl) styleEl.remove(); + + hoveredElement = null; + selectedElement = null; + }; + + window.parent.postMessage({ type: 'webild-editor-ready' }, '*'); +})(); +` + }} + />