} * { box-sizing: border-box; } html, body { margin: 0; padding: 0; } body { background: radial-gradient(circle at top, rgba(37, 99, 235, 0.12), transparent 30%), linear-gradient(180deg, #f8fafc 0%, #eef2ff 100%); color: var(--text-color); font-family: var(--font-family); } a { color: inherit; text-decoration: none; } button, input, textarea, select { font: inherit; } #app { min-height: 100vh; } .lp-node { position: relative; } .lp-column, .lp-row, .lp-scroll-row { display: flex; width: 100%; } .lp-column { flex-direction: column; } .lp-row { flex-direction: row; } .lp-grid { display: grid; width: 100%; } .lp-photo-card { position: relative; width: 100%; background-size: cover; background-repeat: no-repeat; } .lp-overlay, .lp-absolute { position: absolute; inset: 0; } .lp-overlay { pointer-events: none; } .lp-scroll-row { overflow-x: auto; overflow-y: hidden; scrollbar-width: none; } .lp-scroll-row::-webkit-scrollbar { display: none; } .lp-feature-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 12px; width: 100%; } .lp-feature-list li { display: flex; align-items: flex-start; gap: 10px; line-height: 1.5; } .lp-divider { width: 100%; border: 0; border-top: 1px solid currentColor; } .lp-button { display: inline-flex; align-items: center; justify-content: center; gap: 8px; cursor: pointer; transition: transform .2s ease, box-shadow .2s ease, opacity .2s ease; font-weight: 600; } .lp-button:hover { transform: translateY(-1px); box-shadow: 0 12px 24px rgba(15, 23, 42, 0.12); } .lp-image { display: block; max-width: 100%; } img[data-src] { background: #e2e8f0; min-height: 100px; } .lp-icon, .lp-stars { display: inline-flex; align-items: center; justify-content: center; } .lp-badge { display: inline-flex; align-items: center; justify-content: center; width: fit-content; } .lp-empty, .fatal-error { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 32px; } .lp-empty-card, .fatal-error-card { max-width: 720px; background: #111827; color: #e2e8f0; border: 1px solid #334155; border-radius: 16px; padding: 32px; box-shadow: 0 24px 60px rgba(15, 23, 42, 0.24); } .component-error { padding: 20px; margin: 10px 0; background: #fef2f2; border: 1px solid #fca5a5; border-radius: 8px; color: #991b1b; text-align: center; } .component-error button, .fatal-error button { margin-top: 10px; padding: 10px 16px; background: #ef4444; color: #fff; border: none; border-radius: 8px; cursor: pointer; } .unknown-component { padding: 12px 14px; border: 1px dashed #cbd5e1; border-radius: 10px; background: rgba(255, 255, 255, 0.55); color: #475569; } .widget-host { width: 100%; margin: 0; } .widget-iframe { width: 100%; min-height: 400px; background: #f8fafc; border: 0; border-radius: 16px; display: block; } .widget-error { padding: 20px; background: #fff7ed; border: 1px solid #fdba74; border-radius: 12px; color: #9a3412; text-align: center; } .lp-navbar { position: sticky; top: 0; z-index: 40; display: flex; align-items: center; justify-content: space-between; gap: 16px; width: 100%; padding: 16px 20px; background: rgba(15, 23, 42, 0.9); color: #ffffff; backdrop-filter: blur(14px); border-bottom: 1px solid rgba(148, 163, 184, 0.18); } .lp-navbar-logo { font-weight: 700; letter-spacing: 0.02em; } .lp-navbar-links, .lp-navbar-actions { display: flex; align-items: center; gap: 16px; } .lp-navbar-link.active { color: #bfdbfe; } .mobile-hamburger { display: none; background: none; border: none; color: #fff; font-size: 24px; cursor: pointer; padding: 4px; } .desktop-only { display: flex; } .lp-accordion-item { border: 1px solid rgba(148, 163, 184, 0.25); border-radius: 14px; background: rgba(255, 255, 255, 0.72); overflow: hidden; } .lp-accordion-trigger { width: 100%; padding: 16px 18px; border: 0; background: transparent; color: inherit; display: flex; align-items: center; justify-content: space-between; text-align: left; cursor: pointer; } .lp-accordion-panel { padding: 0 18px 18px; display: none; } .lp-accordion-item.is-open .lp-accordion-panel { display: block; } .lp-virtual-grid { overflow-y: auto; position: relative; } .lp-virtual-grid-window { display: grid; gap: inherit; } @media (max-width: 768px) { .mobile-hamburger { display: block; } .desktop-only { display: none; } .lp-navbar { padding: 14px 16px; } .lp-navbar-links { position: fixed; top: 60px; left: -100%; width: 100%; height: calc(100vh - 60px); background: #0f172a; flex-direction: column; align-items: flex-start; padding: 24px 20px; transition: left .3s ease; z-index: 100; overflow-y: auto; } .lp-navbar-links.open { left: 0; } .lp-navbar-actions-mobile { display: flex; flex-direction: column; gap: 12px; width: 100%; } .lp-navbar-actions-mobile .lp-button { width: 100%; } }
sparkles: '✦', zap: '⚡', search: '⌕', brain: '◉', bot: '⬢', link: '🔗', chart: '◔', refresh: '↻', rocket: '🚀', shield: '🛡', layers: '▦', users: '👥', workflow: '⎇', message: '✉', gauge: '◐', check: '✓', wand: '✦', globe: '🌐', monitor: '⌘', building: '🏢', camera: '📷', dumbbell: '🏋', tag: '🏷', star: '★', grid: '▥' }; function normalizeSpacing(value) { if (value === undefined || value === null || value === '') return ''; if (typeof value === 'number') return value + 'px'; return String(value) .split(' ') .map((part) => spacingMap[part] || part) .join(' '); } function setStyle(el, key, value) { if (value === undefined || value === null || value === '') return; el.style[key] = value; } function setText(el, value, fallback) { el.textContent = value || fallback || ''; return el; } function appendChildren(el, children) { (children || []).forEach((child) => { if (!child || typeof child !== 'object') return; if (!child.type && !Array.isArray(child.children)) return; const rendered = renderComponent(child); if (rendered) el.appendChild(rendered); }); return el; } function createElement(tag, className, node) { const el = document.createElement(tag); if (className) el.className = className; if (node && node.type) el.dataset.nodeType = node.type; if (node && node.sectionName) el.dataset.sectionName = node.sectionName; return el; } function applyCommonLayoutStyles(el, node) { setStyle(el, 'background', node.background); setStyle(el, 'padding', normalizeSpacing(node.padding)); setStyle(el, 'gap', normalizeSpacing(node.gap)); setStyle(el, 'alignItems', node.align); setStyle(el, 'justifyContent', node.justify); setStyle(el, 'height', node.height); setStyle(el, 'minHeight', node.minHeight); setStyle(el, 'width', node.width); setStyle(el, 'minWidth', node.minWidth); setStyle(el, 'maxWidth', node.maxWidth); setStyle(el, 'margin', node.margin); setStyle(el, 'borderRadius', node.borderRadius); setStyle(el, 'border', node.border); setStyle(el, 'overflow', node.overflow); setStyle(el, 'boxShadow', node.boxShadow); setStyle(el, 'backdropFilter', node.backdropFilter); if (node.flex !== undefined) el.style.flex = String(node.flex); if (node.flexShrink !== undefined) el.style.flexShrink = String(node.flexShrink); if (node.wrap !== undefined) el.style.flexWrap = node.wrap ? 'wrap' : 'nowrap'; if (node.opacity !== undefined) el.style.opacity = String(node.opacity); if (node.position) el.style.position = node.position; return el; } function createFallbackNode(className, text) { const el = createElement('div', className); el.textContent = text; return el; } class PageState { constructor() { this.state = { mobileMenuOpen: false, openAccordions: new Set(), scrollPosition: 0, activeModal: null }; this.subscribers = []; } setState(updates) { this.state = Object.assign({}, this.state, updates); this.notifySubscribers(); } subscribe(callback) { this.subscribers.push(callback); callback(this.state); return () => { this.subscribers = this.subscribers.filter((cb) => cb !== callback); }; } notifySubscribers() { this.subscribers.forEach((callback) => callback(this.state)); } toggleAccordion(id) { const openAccordions = new Set(this.state.openAccordions); if (openAccordions.has(id)) openAccordions.delete(id); else openAccordions.add(id); this.setState({ openAccordions: openAccordions }); } } const pageState = new PageState(); window.pageState = pageState; class LazyImageLoader { constructor() { this.observer = 'IntersectionObserver' in window ? new IntersectionObserver((entries) => { entries.forEach((entry) => { if (!entry.isIntersecting) return; const img = entry.target; const src = img.dataset.src; if (src) { img.src = src; img.removeAttribute('data-src'); } this.observer.unobserve(img); }); }, { rootMargin: '50px', threshold: 0.01 }) : null; } observe(images) { (images || []).forEach((img) => { if (!img || !img.dataset || !img.dataset.src) return; if (!this.observer) { img.src = img.dataset.src; img.removeAttribute('data-src'); return; } this.observer.observe(img); }); } } const lazyLoader = new LazyImageLoader(); class VirtualGrid { constructor(container, items, itemsPerRow, itemHeight, renderItem) { this.container = container; this.items = items; this.itemsPerRow = itemsPerRow; this.itemHeight = itemHeight; this.renderItem = renderItem; this.windowEl = document.createElement('div'); this.windowEl.className = 'lp-virtual-grid-window'; this.windowEl.style.gridTemplateColumns = 'repeat(' + itemsPerRow + ', minmax(0, 1fr))'; this.windowEl.style.gap = container.style.gap || '16px'; this.container.innerHTML = ''; this.container.appendChild(this.windowEl); this.container.addEventListener('scroll', () => this.render()); this.render(); } render() { const visibleRows = Math.ceil(this.container.clientHeight / this.itemHeight) + 2; const startRow = Math.floor(this.container.scrollTop / this.itemHeight); const endRow = startRow + visibleRows; const startIndex = startRow * this.itemsPerRow; const endIndex = Math.min(endRow * this.itemsPerRow, this.items.length); const spacerTop = startRow * this.itemHeight; const totalRows = Math.ceil(this.items.length / this.itemsPerRow); const spacerBottom = Math.max(0, (totalRows - endRow) * this.itemHeight); this.windowEl.innerHTML = ''; this.windowEl.style.paddingTop = spacerTop + 'px'; this.windowEl.style.paddingBottom = spacerBottom + 'px'; this.items.slice(startIndex, endIndex).forEach((item, index) => { const rendered = this.renderItem(item, startIndex + index); if (rendered) this.windowEl.appendChild(rendered); }); } } class WidgetLoader { static async loadWidget(container, widgetData) { const iframe = document.createElement('iframe'); iframe.className = 'widget-iframe'; iframe.loading = 'lazy'; iframe.setAttribute('scrolling', 'no'); if (widgetData.widget_preview_url) { iframe.sandbox = 'allow-same-origin allow-scripts allow-popups allow-forms'; iframe.src = widgetData.widget_preview_url; } else if (widgetData.widget_data_url) { try { iframe.sandbox = 'allow-same-origin'; iframe.src = widgetData.widget_data_url; } catch (error) { console.error('Failed to load widget data:', error); iframe.srcdoc = ''; } } else if (widgetData.widget_code) { console.error( 'Widget requires widget_preview_url or widget_data_url; widget_code is blocked.' ); const errorNode = document.createElement('div'); errorNode.className = 'widget-error'; errorNode.textContent = 'This widget cannot be rendered in preview.'; container.appendChild(errorNode); return; } else { return; } container.appendChild(iframe); window.addEventListener('message', (event) => { if (event.source !== iframe.contentWindow) return; if (event.data && event.data.type === 'widget-height') { iframe.style.height = String(event.data.height || 400) + 'px'; } }); } } const Components = { column(node) { const el = createElement('div', 'lp-node lp-column', node); applyCommonLayoutStyles(el, node); return appendChildren(el, node.children); }, row(node) { const el = createElement('div', 'lp-node lp-row', node); applyCommonLayoutStyles(el, node); if (node.mobileStack) { el.dataset.mobileStack = 'true'; el.dataset.stackBreakpoint = String(node.stackBreakpoint || 760); el.dataset.mobilePadding = normalizeSpacing(node.mobilePadding || ''); } return appendChildren(el, node.children); }, grid(node) { const el = createElement('div', 'lp-node lp-grid', node); applyCommonLayoutStyles(el, node); el.style.gridTemplateColumns = typeof node.columns === 'number' ? 'repeat(' + node.columns + ', minmax(0, 1fr))' : (node.columns || '1fr'); if (node.mobileColumns) el.dataset.mobileColumns = String(node.mobileColumns); if (node.tabletColumns) el.dataset.tabletColumns = String(node.tabletColumns); if (node.virtualize && Array.isArray(node.children) && node.children.length > 18) { el.classList.add('lp-virtual-grid'); setStyle(el, 'height', node.virtualHeight || '720px'); requestAnimationFrame(() => { new VirtualGrid( el, node.children, Number(node.virtualColumns || node.columns || 3), Number(node.virtualRowHeight || 280), (child) => renderComponent(child) ); }); return el; } return appendChildren(el, node.children); }, 'scroll-row'(node) { const el = createElement('div', 'lp-node lp-scroll-row', node); applyCommonLayoutStyles(el, node); if (node.snap) el.style.scrollSnapType = 'x mandatory'; return appendChildren(el, node.children); }, 'photo-card'(node) { const el = createElement('div', 'lp-node lp-photo-card', node); if (node.bgImage && node.bgGradient) { el.style.backgroundImage = node.bgGradient + ', url("' + node.bgImage + '")'; } else if (node.bgImage) { el.style.backgroundImage = 'url("' + node.bgImage + '")'; } else { setStyle(el, 'background', node.bgGradient); } setStyle(el, 'backgroundPosition', node.objectPosition || 'center center'); setStyle(el, 'height', node.height || '90vh'); setStyle(el, 'borderRadius', node.borderRadius); setStyle(el, 'overflow', node.overflow); if (node.flex !== undefined) el.style.flex = String(node.flex); return appendChildren(el, node.children); }, overlay(node) { const el = createElement('div', 'lp-node lp-overlay', node); setStyle(el, 'background', node.gradient); return appendChildren(el, node.children); }, absolute(node) { const el = createElement('div', 'lp-node lp-absolute', node); setStyle(el, 'top', node.top); setStyle(el, 'left', node.left); setStyle(el, 'right', node.right); setStyle(el, 'bottom', node.bottom); setStyle(el, 'padding', normalizeSpacing(node.padding)); if (node.zIndex !== undefined) el.style.zIndex = String(node.zIndex); return appendChildren(el, node.children); }, text(node) { const el = createElement(node.tag || 'div', 'lp-node', node); setText(el, node.value, ''); setStyle(el, 'fontSize', node.size ? node.size + 'px' : ''); if (node.weight !== undefined) el.style.fontWeight = String(node.weight); setStyle(el, 'color', node.color); setStyle(el, 'textAlign', node.align); if (node.letterSpacing !== undefined) el.style.letterSpacing = node.letterSpacing + 'px'; setStyle(el, 'textTransform', node.transform); if (node.italic) el.style.fontStyle = 'italic'; if (node.lineHeight !== undefined) el.style.lineHeight = String(node.lineHeight); setStyle(el, 'marginBottom', normalizeSpacing(node.marginBottom)); setStyle(el, 'marginTop', normalizeSpacing(node.marginTop)); setStyle(el, 'fontFamily', node.fontFamily); setStyle(el, 'maxWidth', node.maxWidth); return el; }, badge(node) { const el = createElement('span', 'lp-node lp-badge', node); setText(el, node.text, ''); setStyle(el, 'background', node.color); setStyle(el, 'color', node.textColor); setStyle(el, 'padding', '8px 14px'); setStyle(el, 'borderRadius', node.pill ? '999px' : '8px'); if (node.letterSpacing !== undefined) el.style.letterSpacing = node.letterSpacing + 'px'; setStyle(el, 'textTransform', node.transform); return el; }, button(node) { const el = createElement(node.url ? 'a' : 'button', 'lp-node lp-button', node); setText(el, node.text, 'Button'); if (node.url) el.href = node.url; const sizeMap = { sm: '10px 16px', md: '12px 20px', lg: '16px 28px' }; setStyle(el, 'padding', sizeMap[node.size] || sizeMap.md); if (node.borderRadius !== undefined) el.style.borderRadius = String(node.borderRadius) + 'px'; if (node.letterSpacing !== undefined) el.style.letterSpacing = node.letterSpacing + 'px'; setStyle(el, 'textTransform', node.transform); if (node.fullWidth) el.style.width = '100%'; if (node.variant === 'ghost-border') { el.style.background = 'transparent'; el.style.border = '1px solid ' + (node.color || '#cbd5e1'); el.style.color = node.textColor || node.color || '#0f172a'; } else if (node.variant === 'ghost') { el.style.background = 'transparent'; el.style.border = 'none'; el.style.color = node.textColor || node.color || '#0f172a'; } else if (node.variant === 'white') { el.style.background = '#ffffff'; el.style.border = 'none'; el.style.color = node.color || '#0f172a'; } else if (node.variant === 'underline') { el.style.background = 'transparent'; el.style.border = 'none'; el.style.textDecoration = 'underline'; el.style.color = node.textColor || node.color || '#0f172a'; } else { el.style.background = node.color || '#2563eb'; el.style.border = 'none'; el.style.color = node.textColor || '#ffffff'; } return el; }, image(node) { const el = createElement('img', 'lp-node lp-image', node); if (node.lazy !== false && node.src) el.dataset.src = node.src; else el.src = node.src || ''; el.alt = node.alt || ''; setStyle(el, 'width', node.width); setStyle(el, 'height', node.height); setStyle(el, 'minHeight', node.minHeight); setStyle(el, 'borderRadius', node.borderRadius); setStyle(el, 'objectFit', node.objectFit); setStyle(el, 'objectPosition', node.objectPosition); if (node.flex !== undefined) el.style.flex = String(node.flex); return el; }, icon(node) { const el = createElement('span', 'lp-node lp-icon', node); setText(el, node.emoji || iconMap[node.name] || '✦'); setStyle(el, 'color', node.color); setStyle(el, 'background', node.background); if (node.size !== undefined) el.style.fontSize = node.size + 'px'; if (node.padding !== undefined) el.style.padding = node.padding + 'px'; if (node.borderRadius !== undefined) el.style.borderRadius = node.borderRadius + 'px'; return el; }, 'icon-link'(node) { const el = createElement('div', 'lp-node lp-row', node); el.style.gap = '8px'; el.style.alignItems = 'center'; el.style.justifyContent = node.align === 'left' ? 'flex-start' : 'center'; const icon = document.createElement('span'); icon.textContent = node.icon || '•'; const label = document.createElement('span'); label.textContent = node.text || ''; label.style.color = node.color || '#64748b'; el.appendChild(icon); el.appendChild(label); return el; }, 'feature-list'(node) { const el = createElement('ul', 'lp-node lp-feature-list', node); (node.items || []).forEach((item) => { const li = document.createElement('li'); const bullet = document.createElement('span'); bullet.textContent = node.style === 'dotlist' ? '•' : '✓'; bullet.style.color = node.color || '#2563eb'; const text = document.createElement('span'); text.textContent = item; text.style.color = node.textColor || '#334155'; li.appendChild(bullet); li.appendChild(text); el.appendChild(li); }); return el; }, 'price-block'(node) { const el = createElement('div', 'lp-node lp-column', node); applyCommonLayoutStyles(el, node); setStyle(el, 'background', node.background || 'var(--surface-color)'); setStyle(el, 'border', node.border || '1px solid var(--surface-border)'); setStyle(el, 'padding', normalizeSpacing(node.padding || 'lg')); setStyle(el, 'borderRadius', node.borderRadius || '20px'); if (node.title) el.appendChild(Components.text({ type: 'text', value: node.title, size: 18, weight: 700 })); if (node.price) el.appendChild(Components.text({ type: 'text', value: node.price, size: 36, weight: 800 })); if (node.description) el.appendChild(Components.text({ type: 'text', value: node.description, color: '#475569' })); if (Array.isArray(node.items)) { el.appendChild(Components['feature-list']({ type: 'feature-list', items: node.items, color: node.color || '#2563eb' })); } if (node.ctaText) { el.appendChild(Components.button({ type: 'button', text: node.ctaText, url: node.ctaUrl, color: node.color || '#2563eb', fullWidth: true })); } return el; }, stars(node) { const el = createElement('div', 'lp-node lp-stars', node); el.textContent = '★'.repeat(node.rating || 0); setStyle(el, 'color', node.color || '#f59e0b'); if (node.size !== undefined) el.style.fontSize = node.size + 'px'; return el; }, divider(node) { const el = createElement('hr', 'lp-node lp-divider', node); setStyle(el, 'color', node.color || '#cbd5e1'); setStyle(el, 'opacity', node.opacity !== undefined ? String(node.opacity) : ''); setStyle(el, 'margin', normalizeSpacing(node.margin)); return el; }, spacer(node) { const el = createElement('div', 'lp-node', node); if (node.size !== undefined) el.style.height = node.size + 'px'; else el.style.flex = '1'; return el; }, navbar(node) { const nav = createElement('nav', 'lp-navbar', node); const logo = createElement('div', 'lp-navbar-logo', node); logo.textContent = (node.logo && node.logo.text) || node.title || 'Brand'; const hamburger = createElement('button', 'mobile-hamburger', node); hamburger.type = 'button'; hamburger.setAttribute('aria-label', 'Toggle navigation'); hamburger.textContent = '☰'; hamburger.addEventListener('click', () => { pageState.setState({ mobileMenuOpen: !pageState.state.mobileMenuOpen }); }); const links = createElement('div', 'lp-navbar-links', node); (node.links || []).forEach((link) => { const anchor = document.createElement('a'); anchor.className = 'lp-navbar-link' + (link.active ? ' active' : ''); anchor.href = link.href || '#'; anchor.textContent = link.text || 'Link'; links.appendChild(anchor); }); const mobileActions = createElement('div', 'lp-navbar-actions-mobile', node); (node.actions || []).forEach((action) => { mobileActions.appendChild(Components.button({ type: 'button', text: action.text, url: action.href || action.url, variant: action.variant || 'ghost-border', color: action.color || '#ffffff', textColor: action.textColor || '#ffffff' })); }); links.appendChild(mobileActions); const actions = createElement('div', 'lp-navbar-actions desktop-only', node); (node.actions || []).forEach((action) => { actions.appendChild(Components.button({ type: 'button', text: action.text, url: action.href || action.url, variant: action.variant || 'ghost-border', color: action.color || '#ffffff', textColor: action.textColor || '#ffffff' })); }); pageState.subscribe((state) => { links.classList.toggle('open', state.mobileMenuOpen); }); nav.appendChild(logo); nav.appendChild(hamburger); nav.appendChild(links); nav.appendChild(actions); return nav; }, faq(node) { const el = createElement('section', 'lp-node lp-column', node); applyCommonLayoutStyles(el, node); (node.items || node.faq_items || []).forEach((item, index) => { const itemId = (node.id || 'faq') + '-' + index; const wrapper = createElement('div', 'lp-accordion-item', node); const trigger = createElement('button', 'lp-accordion-trigger', node); trigger.type = 'button'; trigger.textContent = item.question || item.title || 'Question'; const marker = createElement('span', '', node); marker.textContent = '+'; trigger.appendChild(marker); const panel = createElement('div', 'lp-accordion-panel', node); panel.textContent = item.answer || item.description || ''; trigger.addEventListener('click', () => pageState.toggleAccordion(itemId)); pageState.subscribe((state) => { const isOpen = state.openAccordions.has(itemId); wrapper.classList.toggle('is-open', isOpen); marker.textContent = isOpen ? '−' : '+'; }); wrapper.appendChild(trigger); wrapper.appendChild(panel); el.appendChild(wrapper); }); return el; }, hero(node) { const el = createElement('section', 'lp-node lp-column', node); applyCommonLayoutStyles(el, node); el.dataset.sectionName = 'hero'; return appendChildren(el, node.children); }, features(node) { const el = createElement('section', 'lp-node lp-column', node); applyCommonLayoutStyles(el, node); el.dataset.sectionName = 'features'; return appendChildren(el, node.children); }, testimonials(node) { const el = createElement('section', 'lp-node lp-column', node); applyCommonLayoutStyles(el, node); el.dataset.sectionName = 'testimonials'; return appendChildren(el, node.children); }, cta(node) { const el = createElement('section', 'lp-node lp-column', node); applyCommonLayoutStyles(el, node); el.dataset.sectionName = 'cta'; return appendChildren(el, node.children); }, footer(node) { const el = createElement('footer', 'lp-node lp-column', node); applyCommonLayoutStyles(el, node); el.dataset.sectionName = 'footer'; return appendChildren(el, node.children); }, render(node) { if (!node || typeof node !== 'object') return document.createTextNode(''); const component = this[node.type]; if (!component) { console.warn('Unknown type:', node.type); return this.fallback(node); } try { return component.call(this, node); } catch (error) { console.error('Error rendering ' + node.type + ':', error); return this.errorFallback(node, error); } }, fallback(node) { return createFallbackNode( 'unknown-component', 'Unknown component: ' + (node && node.type ? node.type : 'undefined') ); }, errorFallback(node) { const wrapper = createElement('div', 'component-error', node); const message = document.createElement('p'); message.textContent = 'Failed to load ' + (node.type || 'component'); const retry = document.createElement('button'); retry.type = 'button'; retry.textContent = 'Retry'; retry.addEventListener('click', () => { const replacement = renderComponent(node, 1); wrapper.replaceWith(replacement); }); wrapper.appendChild(message); wrapper.appendChild(retry); return wrapper; } }; function renderComponent(node, retryCount) { if (!node || typeof node !== 'object') return document.createTextNode(''); const retries = retryCount || 0; const maxRetries = 2; try { return Components.render(node); } catch (error) { console.error('Render error (' + (node && node.type) + '):', error); if (retries < maxRetries) { return renderComponent(node, retries + 1); } return Components.errorFallback(node || { type: 'unknown' }, error); } } function applyResponsiveRules(root) { const viewport = window.innerWidth; root.querySelectorAll('[data-mobile-stack="true"]').forEach((el) => { const breakpoint = parseInt(el.dataset.stackBreakpoint || '760', 10); if (viewport <= breakpoint) { el.style.flexDirection = 'column'; if (el.dataset.mobilePadding) el.style.padding = el.dataset.mobilePadding; } else { el.style.flexDirection = 'row'; } }); root.querySelectorAll('.lp-grid').forEach((el) => { if (!el.dataset.originalColumns && el.style.gridTemplateColumns) { el.dataset.originalColumns = el.style.gridTemplateColumns; } const originalColumns = el.dataset.originalColumns || el.style.gridTemplateColumns; const mobileColumns = el.dataset.mobileColumns; const tabletColumns = el.dataset.tabletColumns; if (viewport <= 640 && mobileColumns) { el.style.gridTemplateColumns = 'repeat(' + mobileColumns + ', minmax(0, 1fr))'; } else if (viewport <= 1024 && tabletColumns) { el.style.gridTemplateColumns = 'repeat(' + tabletColumns + ', minmax(0, 1fr))'; } else if (originalColumns) { el.style.gridTemplateColumns = originalColumns; } }); } function createFatalError(error) { const outer = createElement('div', 'fatal-error'); const card = createElement('div', 'fatal-error-card'); const heading = document.createElement('h2'); heading.textContent = 'Failed to load page'; const body = document.createElement('p'); body.textContent = 'The preview deployment could not be rendered.'; const button = document.createElement('button'); button.type = 'button'; button.textContent = 'Retry'; button.addEventListener('click', () => window.location.reload()); card.appendChild(heading); card.appendChild(body); if (error && error.message) { const details = document.createElement('p'); details.textContent = error.message; card.appendChild(details); } card.appendChild(button); outer.appendChild(card); return outer; } function findWidgetAnchor(app, placementSection) { if (!placementSection) return null; const explicitAnchor = app.querySelector('[data-section-name="' + placementSection + '"]') || app.querySelector('[data-node-type="' + placementSection + '"]'); if (explicitAnchor) return explicitAnchor; if (placementSection === 'hero') return app.firstElementChild; if (placementSection === 'footer') return app.lastElementChild; return null; } async function injectWidget(widgetSection) { if (!widgetSection) return; const app = document.getElementById('app'); const placementSection = widgetSection.placement_section || 'hero'; const anchor = findWidgetAnchor(app, placementSection); const host = createElement('div', 'widget-host'); if (placementSection === 'hero') { if (anchor && anchor.parentNode) { anchor.parentNode.insertBefore(host, anchor.nextSibling); } else { app.insertBefore(host, app.firstChild); } } else if (placementSection === 'footer' && anchor && anchor.parentNode) { anchor.parentNode.insertBefore(host, anchor); } else if (anchor) { anchor.appendChild(host); } else { app.appendChild(host); } try { await WidgetLoader.loadWidget(host, widgetSection); } catch (error) { console.error('Widget injection error:', error); host.replaceChildren(createFallbackNode('widget-error', 'Failed to load widget')); } } function setupScrollSpy() { const navLinks = Array.from(document.querySelectorAll('.lp-navbar-link[href^="#"]')); if (!navLinks.length || !('IntersectionObserver' in window)) return; const sections = navLinks .map((link) => document.querySelector(link.getAttribute('href'))) .filter(Boolean); const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { const id = entry.target.id; const link = document.querySelector('.lp-navbar-link[href="#' + id + '"]'); if (!link) return; link.classList.toggle('active', entry.isIntersecting); }); }, { rootMargin: '-30% 0px -55% 0px', threshold: 0.01 }); sections.forEach((section) => observer.observe(section)); } async function renderLandingPage() { const startTime = performance.now(); const app = document.getElementById('app'); try { const response = await fetch('./landing-page.json', { cache: 'no-store' }); if (!response.ok) throw new Error('Failed to load landing-page.json'); const landingPage = await response.json(); const config = landingPage.config_json || {}; const theme = landingPage.theme || config.theme || {}; const layout = landingPage.layout || config.layout || {}; document.title = landingPage.name || 'Landing Page Preview'; document.documentElement.style.setProperty( '--font-family', theme.font || 'Inter, Arial, sans-serif' ); document.documentElement.style.setProperty( '--page-bg', theme.bg || layout.background || '#f8fafc' ); document.documentElement.style.setProperty( '--accent-color', theme.primary || '#2563eb' ); const rootNode = renderComponent( layout && typeof layout === 'object' && !layout.type ? Object.assign({ type: 'column' }, layout) : (layout || { type: 'column', children: [] }) ); const fragment = document.createDocumentFragment(); fragment.appendChild(rootNode); app.replaceChildren(fragment); applyResponsiveRules(app); lazyLoader.observe(Array.from(app.querySelectorAll('img[data-src]'))); setupScrollSpy(); await injectWidget(landingPage.widget_section); const onResize = () => applyResponsiveRules(app); window.addEventListener('resize', onResize, { passive: true }); window.addEventListener('scroll', () => { pageState.setState({ scrollPosition: window.scrollY || window.pageYOffset || 0 }); }, { passive: true }); const endTime = performance.now(); console.log('Page rendered in ' + (endTime - startTime).toFixed(2) + 'ms'); } catch (error) { console.error('Failed to render landing page:', error); app.replaceChildren(createFatalError(error)); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', renderLandingPage); } else {