Files
95a9cf9e-0d01-4efa-b133-6ab…/src/app/layout.tsx
2026-01-12 14:07:08 +02:00

1266 lines
40 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { Metadata } from "next";
import { Inter_Tight } from "next/font/google";
import "./globals.css";
import { ServiceWrapper } from "@/components/ServiceWrapper";
import Tag from "@/tag/Tag";
const interTight = Inter_Tight({
variable: "--font-inter-tight", subsets: ["latin"],
weight: ["100", "200", "300", "400", "500", "600", "700", "800", "900"],
});
export const metadata: Metadata = {
title: "Artisan Discover & Own One-of-a-Kind Art", description: "Explore curated contemporary and emerging artwork from talented artists worldwide. Connect with collectors and invest in exceptional pieces.", keywords: "art gallery, contemporary art, emerging artists, art collection, buy art online, fine art, curated artwork", metadataBase: new URL("https://artisan-gallery.com"),
alternates: {
canonical: "https://artisan-gallery.com"
},
openGraph: {
title: "Artisan Discover & Own One-of-a-Kind Art", description: "Explore curated collections from emerging and established artists around the world.", type: "website", siteName: "Artisan", images: [{
url: "https://img.b2bpic.net/free-photo/decorations-used-painting-inspiration-creativity-placed-art-gallery_482257-127281.jpg", alt: "Curated contemporary art exhibition"
}]
},
twitter: {
card: "summary_large_image", title: "Artisan Discover & Own One-of-a-Kind Art", description: "Explore curated collections from emerging and established artists around the world.", images: ["https://img.b2bpic.net/free-photo/decorations-used-painting-inspiration-creativity-placed-art-gallery_482257-127281.jpg"]
},
robots: {
index: true,
follow: true
}
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" suppressHydrationWarning>
<ServiceWrapper>
<body
className={`${interTight.variable} antialiased`}
>
<Tag />
{children}
<script
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 {' +
' 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';
}
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('.webildsbx.cc/')) {
try {
const urlObj = new URL(url);
return urlObj.pathname;
} catch (e) {
return url;
}
}
return url;
};
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
};
}
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';
if (isImage) 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' || computedStyle.backgroundImage !== 'none' || element.querySelector('img');
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';
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.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 (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 isBackground = element.tagName.toLowerCase() !== 'img';
if (isBackground) {
element.style.backgroundImage = change.oldValue ? 'url(' + change.oldValue + ')' : '';
} 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 } = 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;
element.src = newSrc;
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);
window.webildCleanup = () => {
isActive = false;
if (selectedElement) {
makeUneditable(selectedElement, false);
}
removeHoverOverlay();
removeElementTypeLabel();
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' }, '*');
})();
`
}}
/>
</body>
</ServiceWrapper>
</html>
);
}