/** * OTS Theme - Custom Block Editor Scripts (InnerBlocks Architecture) * * 13 blocks: 5 standalone + 4 parent/child pairs. * Parent blocks use InnerBlocks for child items. * Child blocks are dynamic (save -> null) with inline RichText editing. */ (function (wp) { 'use strict'; var el = wp.element.createElement; var Frag = wp.element.Fragment; var reg = wp.blocks.registerBlockType; var RT = wp.blockEditor.RichText; var IC = wp.blockEditor.InspectorControls; var IB = wp.blockEditor.InnerBlocks; var PB = wp.components.PanelBody; var TC = wp.components.TextControl; var TA = wp.components.TextareaControl; var SC = wp.components.SelectControl; var TG = wp.components.ToggleControl; var RC = wp.components.RangeControl; var Btn = wp.components.Button; var MUC = wp.blockEditor.MediaUploadCheck; var MU = wp.blockEditor.MediaUpload; var useS = wp.element.useState; /* ── Simple array helpers (for pricing-card features) ────────────────── */ function arrSet(arr, i, val) { var c = arr.slice(); c[i] = val; return c; } function arrAdd(arr, val) { return arr.concat([val]); } function arrRm(arr, i) { var c = arr.slice(); c.splice(i, 1); return c; } /* ── Shared card image attributes ────────────────────────────────────── */ var CARD_IMAGE_ATTRS = { imgId: { type: 'number', default: 0 }, imgUrl: { type: 'string', default: '' }, imgAlt: { type: 'string', default: '' }, imgWidth: { type: 'number', default: 80 }, imgHeight: { type: 'number', default: 0 }, imgFit: { type: 'string', default: 'contain' }, imgPosition: { type: 'string', default: 'top' } }; /* ── Shared icon attributes ──────────────────────────────────────────── */ var CARD_ICON_ATTRS = { icon: { type: 'string', default: '' }, iconType: { type: 'string', default: 'emoji' }, faIcon: { type: 'string', default: '' } }; /** * Build icon InspectorControls: a toggle that switches between emoji and * Font Awesome mode, with the appropriate text input shown beneath it. */ /* ── Font Awesome icon catalogue ─────────────────────────────────────── */ // Format: ['s' = fas solid | 'b' = fab brand, 'icon-name'] var FA_ICONS = [ // Navigation ['s', 'angle-down'], ['s', 'angle-left'], ['s', 'angle-right'], ['s', 'angle-up'], ['s', 'angles-down'], ['s', 'angles-left'], ['s', 'angles-right'], ['s', 'angles-up'], ['s', 'arrow-down'], ['s', 'arrow-left'], ['s', 'arrow-right'], ['s', 'arrow-up'], ['s', 'arrow-rotate-left'], ['s', 'arrow-rotate-right'], ['s', 'arrows-rotate'], ['s', 'arrows-up-down-left-right'], ['s', 'caret-down'], ['s', 'caret-left'], ['s', 'caret-right'], ['s', 'caret-up'], ['s', 'chevron-down'], ['s', 'chevron-left'], ['s', 'chevron-right'], ['s', 'chevron-up'], ['s', 'circle-arrow-down'], ['s', 'circle-arrow-left'], ['s', 'circle-arrow-right'], ['s', 'circle-arrow-up'], // Interface ['s', 'asterisk'], ['s', 'at'], ['s', 'ban'], ['s', 'bars'], ['s', 'bell'], ['s', 'bell-slash'], ['s', 'bolt'], ['s', 'check'], ['s', 'check-double'], ['s', 'circle-check'], ['s', 'circle-exclamation'], ['s', 'circle-info'], ['s', 'circle-minus'], ['s', 'circle-plus'], ['s', 'circle-question'], ['s', 'circle-xmark'], ['s', 'ellipsis'], ['s', 'ellipsis-vertical'], ['s', 'eye'], ['s', 'eye-slash'], ['s', 'filter'], ['s', 'grip'], ['s', 'grip-vertical'], ['s', 'hashtag'], ['s', 'key'], ['s', 'lock'], ['s', 'lock-open'], ['s', 'magnifying-glass'], ['s', 'minus'], ['s', 'plus'], ['s', 'power-off'], ['s', 'rotate'], ['s', 'shield'], ['s', 'shield-check'], ['s', 'shield-halved'], ['s', 'sliders'], ['s', 'sort'], ['s', 'sort-down'], ['s', 'sort-up'], ['s', 'spinner'], ['s', 'star'], ['s', 'star-half-stroke'], ['s', 'toggle-off'], ['s', 'toggle-on'], ['s', 'xmark'], // Text editing ['s', 'align-center'], ['s', 'align-justify'], ['s', 'align-left'], ['s', 'align-right'], ['s', 'bold'], ['s', 'code'], ['s', 'crop'], ['s', 'eraser'], ['s', 'highlight'], ['s', 'indent'], ['s', 'italic'], ['s', 'link'], ['s', 'link-slash'], ['s', 'list'], ['s', 'list-check'], ['s', 'list-ol'], ['s', 'list-ul'], ['s', 'outdent'], ['s', 'pen'], ['s', 'pen-to-square'], ['s', 'pencil'], ['s', 'quote-left'], ['s', 'quote-right'], ['s', 'scissors'], ['s', 'strikethrough'], ['s', 'subscript'], ['s', 'superscript'], ['s', 'table'], ['s', 'table-list'], ['s', 'underline'], // Files and documents ['s', 'book'], ['s', 'book-open'], ['s', 'bookmark'], ['s', 'box'], ['s', 'box-archive'], ['s', 'boxes-stacked'], ['s', 'clipboard'], ['s', 'clipboard-check'], ['s', 'clipboard-list'], ['s', 'copy'], ['s', 'database'], ['s', 'download'], ['s', 'envelope'], ['s', 'envelope-open'], ['s', 'file'], ['s', 'file-arrow-down'], ['s', 'file-arrow-up'], ['s', 'file-code'], ['s', 'file-excel'], ['s', 'file-image'], ['s', 'file-lines'], ['s', 'file-pdf'], ['s', 'file-powerpoint'], ['s', 'file-word'], ['s', 'file-zipper'], ['s', 'floppy-disk'], ['s', 'folder'], ['s', 'folder-minus'], ['s', 'folder-open'], ['s', 'folder-plus'], ['s', 'inbox'], ['s', 'newspaper'], ['s', 'paper-plane'], ['s', 'paperclip'], ['s', 'print'], ['s', 'share'], ['s', 'share-nodes'], ['s', 'upload'], // Technology ['s', 'barcode'], ['s', 'bug'], ['s', 'cloud'], ['s', 'cloud-arrow-down'], ['s', 'cloud-arrow-up'], ['s', 'code-branch'], ['s', 'cpu'], ['s', 'desktop'], ['s', 'display'], ['s', 'ethernet'], ['s', 'gear'], ['s', 'gears'], ['s', 'hard-drive'], ['s', 'keyboard'], ['s', 'laptop'], ['s', 'memory'], ['s', 'microchip'], ['s', 'mobile'], ['s', 'mobile-screen'], ['s', 'network-wired'], ['s', 'plug'], ['s', 'qrcode'], ['s', 'robot'], ['s', 'satellite'], ['s', 'satellite-dish'], ['s', 'server'], ['s', 'tablet'], ['s', 'terminal'], ['s', 'toolbox'], ['s', 'wifi'], ['s', 'wrench'], // Media and communication ['s', 'backward'], ['s', 'backward-fast'], ['s', 'camera'], ['s', 'camera-retro'], ['s', 'clapperboard'], ['s', 'comment'], ['s', 'comment-dots'], ['s', 'comments'], ['s', 'film'], ['s', 'forward'], ['s', 'forward-fast'], ['s', 'headphones'], ['s', 'message'], ['s', 'microphone'], ['s', 'microphone-slash'], ['s', 'music'], ['s', 'pause'], ['s', 'phone'], ['s', 'phone-slash'], ['s', 'phone-volume'], ['s', 'photo-film'], ['s', 'play'], ['s', 'radio'], ['s', 'repeat'], ['s', 'rss'], ['s', 'shuffle'], ['s', 'stop'], ['s', 'tv'], ['s', 'video'], ['s', 'video-slash'], ['s', 'voicemail'], ['s', 'volume-high'], ['s', 'volume-low'], ['s', 'volume-xmark'], // People ['s', 'address-book'], ['s', 'address-card'], ['s', 'baby'], ['s', 'child'], ['s', 'circle-user'], ['s', 'people-group'], ['s', 'person'], ['s', 'person-running'], ['s', 'person-walking'], ['s', 'thumbs-down'], ['s', 'thumbs-up'], ['s', 'user'], ['s', 'user-check'], ['s', 'user-group'], ['s', 'user-minus'], ['s', 'user-plus'], ['s', 'user-slash'], ['s', 'user-tie'], ['s', 'users'], // Location and maps ['s', 'building'], ['s', 'buildings'], ['s', 'city'], ['s', 'compass'], ['s', 'earth-americas'], ['s', 'earth-asia'], ['s', 'earth-europe'], ['s', 'flag'], ['s', 'globe'], ['s', 'house'], ['s', 'location-dot'], ['s', 'location-pin'], ['s', 'map'], ['s', 'map-pin'], ['s', 'mountain'], ['s', 'road'], ['s', 'route'], ['s', 'signs-post'], // Business and finance ['s', 'award'], ['s', 'briefcase'], ['s', 'bullhorn'], ['s', 'bullseye'], ['s', 'calendar'], ['s', 'calendar-check'], ['s', 'calendar-days'], ['s', 'calendar-minus'], ['s', 'calendar-plus'], ['s', 'certificate'], ['s', 'chart-bar'], ['s', 'chart-column'], ['s', 'chart-line'], ['s', 'chart-pie'], ['s', 'chart-simple'], ['s', 'clock'], ['s', 'coins'], ['s', 'credit-card'], ['s', 'crown'], ['s', 'gem'], ['s', 'gift'], ['s', 'handshake'], ['s', 'medal'], ['s', 'money-bill'], ['s', 'money-bill-wave'], ['s', 'piggy-bank'], ['s', 'receipt'], ['s', 'stopwatch'], ['s', 'suitcase'], ['s', 'tag'], ['s', 'tags'], ['s', 'timer'], ['s', 'trophy'], ['s', 'truck'], ['s', 'wallet'], // Nature and weather ['s', 'fire'], ['s', 'fire-flame-curved'], ['s', 'leaf'], ['s', 'moon'], ['s', 'seedling'], ['s', 'snowflake'], ['s', 'sun'], ['s', 'thermometer'], ['s', 'tree'], ['s', 'water'], ['s', 'wind'], // Health and medical ['s', 'bandage'], ['s', 'bone'], ['s', 'brain'], ['s', 'dna'], ['s', 'heart'], ['s', 'heart-pulse'], ['s', 'hospital'], ['s', 'pills'], ['s', 'stethoscope'], ['s', 'syringe'], ['s', 'tooth'], ['s', 'virus'], ['s', 'wheelchair'], // Brands ['b', 'android'], ['b', 'angular'], ['b', 'apple'], ['b', 'aws'], ['b', 'bitbucket'], ['b', 'bootstrap'], ['b', 'chrome'], ['b', 'css3'], ['b', 'discord'], ['b', 'docker'], ['b', 'dribbble'], ['b', 'dropbox'], ['b', 'facebook'], ['b', 'facebook-f'], ['b', 'figma'], ['b', 'firefox'], ['b', 'git'], ['b', 'github'], ['b', 'gitlab'], ['b', 'google'], ['b', 'html5'], ['b', 'instagram'], ['b', 'java'], ['b', 'linkedin'], ['b', 'linux'], ['b', 'microsoft'], ['b', 'node-js'], ['b', 'npm'], ['b', 'php'], ['b', 'python'], ['b', 'react'], ['b', 'slack'], ['b', 'square-js'], ['b', 'tiktok'], ['b', 'vuejs'], ['b', 'whatsapp'], ['b', 'windows'], ['b', 'wordpress'], ['b', 'x-twitter'], ['b', 'youtube'] ]; /* ── Icon picker component ───────────────────────────────────────────── */ function IconPicker(props) { var value = props.value || ''; var onChange = props.onChange; var qs = useS(''); var query = qs[0], setQuery = qs[1]; var lower = query.toLowerCase().replace(/\s+/g, '-'); var filtered = query ? FA_ICONS.filter(function (ic) { return ic[1].indexOf(lower) !== -1; }) : FA_ICONS; return el('div', { className: 'oribi-icon-picker' }, // Current selection row value ? el('div', { className: 'oribi-icon-current' }, el('i', { className: value, 'aria-hidden': 'true' }), el('span', { className: 'oribi-icon-current-label' }, value), el('button', { className: 'oribi-icon-clear', type: 'button', onClick: function () { onChange(''); } }, '\u2715 Clear') ) : null, // Search input el('input', { type: 'search', className: 'oribi-icon-search', placeholder: 'Search ' + FA_ICONS.length + ' icons…', value: query, onChange: function (e) { setQuery(e.target.value); } }), // Result count badge (only when filtering) query ? el('div', { className: 'oribi-icon-count' }, filtered.length + ' result' + (filtered.length === 1 ? '' : 's')) : null, // Icon grid el('div', { className: 'oribi-icon-grid' }, filtered.length === 0 ? el('div', { className: 'oribi-icon-empty' }, 'No icons found.') : filtered.map(function (ic) { var prefix = ic[0] === 'b' ? 'fab' : 'fas'; var cls = prefix + ' fa-' + ic[1]; var active = cls === value; return el('button', { key: cls, type: 'button', title: ic[1], className: 'oribi-icon-cell' + (active ? ' is-active' : ''), onClick: function () { onChange(cls); } }, el('i', { className: cls, 'aria-hidden': 'true' }), el('span', null, ic[1]) ); }) ) ); } function iconControls(a, s) { var useFa = a.iconType === 'fontawesome'; return el(Frag, null, el(TG, { label: 'Use Font Awesome icon', checked: useFa, onChange: function (v) { s({ iconType: v ? 'fontawesome' : 'emoji' }); } }), useFa ? el(IconPicker, { value: a.faIcon || '', onChange: function (v) { s({ faIcon: v }); } }) : el(TC, { label: 'Icon (emoji)', value: a.icon || '', onChange: function (v) { s({ icon: v }); } }) ); } /** * Return the element to render in the card preview for the current icon state. * cssClass - the wrapper class, e.g. 'feature-icon' or 'value-icon' * extraStyle - optional inline style object for the wrapper */ function iconPreview(a, cssClass, extraStyle) { var useFa = a.iconType === 'fontawesome'; var hasIcon = useFa ? !!a.faIcon : !!a.icon; if (!hasIcon) return null; var child = useFa ? el('i', { className: a.faIcon || '', 'aria-hidden': 'true' }) : a.icon; return el('div', { className: cssClass, style: extraStyle || {} }, child); } /** Build shared Card Image InspectorControls panel. */ function cardImageControls(a, s) { var imgW = a.imgWidth || 80; var imgH = a.imgHeight || 0; var imgPos = a.imgPosition || 'top'; var imgFit = a.imgFit || 'contain'; return el(PB, { title: 'Card Image', initialOpen: false }, el(MUC, null, el(MU, { onSelect: function (media) { s({ imgId: media.id, imgUrl: media.url, imgAlt: media.alt || '' }); }, allowedTypes: ['image'], value: a.imgId || 0, render: function (ref) { return el(Frag, null, a.imgUrl ? el('div', { style: { marginBottom: '8px' } }, el('img', { src: a.imgUrl, style: { maxWidth: '100%', height: 'auto', borderRadius: '4px', display: 'block', marginBottom: '6px' } }), el(Btn, { variant: 'link', isDestructive: true, onClick: function () { s({ imgId: 0, imgUrl: '', imgAlt: '' }); } }, 'Remove image') ) : null, el(Btn, { onClick: ref.open, variant: 'secondary', __next40pxDefaultSize: true }, a.imgUrl ? 'Replace image' : 'Select from Media Library') ); } }) ), a.imgUrl ? el(Frag, null, el(RC, { label: 'Width (px)', value: imgW, min: 20, max: 600, step: 4, onChange: function (v) { s({ imgWidth: v }); } }), el(RC, { label: 'Height (px) - 0 = auto', value: imgH, min: 0, max: 600, step: 4, onChange: function (v) { s({ imgHeight: v }); } }), el(TG, { label: 'Scale to fill (cover)', checked: imgFit === 'cover', onChange: function (v) { s({ imgFit: v ? 'cover' : 'contain' }); } }), el(SC, { label: 'Position', value: imgPos, options: [ { label: 'Above content', value: 'top' }, { label: 'Left of content', value: 'left' }, { label: 'Replace icon', value: 'replace-icon' }, { label: 'Background', value: 'background' } ], onChange: function (v) { s({ imgPosition: v }); } }) ) : null ); } /** Build an image preview element for the editor. */ function cardImagePreview(a) { if (!a.imgUrl) return null; var imgW = a.imgWidth || 80; var imgH = a.imgHeight || 0; var imgFit = a.imgFit || 'contain'; var style = { width: imgW + 'px', maxWidth: '100%', height: imgH > 0 ? imgH + 'px' : 'auto', borderRadius: '4px', objectFit: imgFit, display: 'block' }; return el('div', { className: 'oribi-card-img-wrap', style: { marginBottom: '1.25rem' } }, el('img', { src: a.imgUrl, className: 'oribi-card-img oribi-card-img--' + imgFit, style: style }) ); } /** Build a shared card section edit component. */ function createCardSectionEdit(allowedBlocks, defaultTemplate, label) { return function (props) { var a = props.attributes, s = props.setAttributes; return el(Frag, null, el(IC, null, el(PB, { title: 'Section Settings' }, el(SC, { label: 'Background', value: a.variant, options: [ { label: 'Normal', value: 'normal' }, { label: 'Alternate', value: 'alt' } ], onChange: function (v) { s({ variant: v }); } }), el(RC, { label: 'Columns', value: a.columns, min: 1, max: 4, onChange: function (v) { s({ columns: v }); } }), el(TC, { label: 'Label', value: a.label, onChange: function (v) { s({ label: v }); } }) ) ), el('section', { className: a.variant === 'alt' ? 'section section-alt' : 'section' }, el('div', { className: 'container' }, el('div', { className: 'section-header' }, a.label ? el('span', { className: 'section-label' }, a.label) : null, el(RT, { tagName: 'h2', value: a.heading, onChange: function (v) { s({ heading: v }); }, placeholder: 'Section heading...' }), el(RT, { tagName: 'p', className: 'lead', value: a.lead, onChange: function (v) { s({ lead: v }); }, placeholder: 'Lead text...' }) ), el('div', { className: 'grid-' + a.columns }, el(IB, { allowedBlocks: allowedBlocks, template: defaultTemplate, templateLock: false }) ) ) ) ); }; } /** Standard section attributes for JS. */ var SECTION_ATTRS = { align: { type: 'string', default: 'full' }, variant: { type: 'string', default: 'normal' }, label: { type: 'string', default: '' }, heading: { type: 'string', default: '' }, lead: { type: 'string', default: '' }, columns: { type: 'number', default: 3 } }; /* ═══════════════════════════════════════════════════════════════════════ STANDALONE BLOCKS (unchanged architecture) ═══════════════════════════════════════════════════════════════════════ */ /* 1. HERO ─────────────────────────────────────────────────────────────── */ reg('oribi/hero', { title: 'Oribi Hero', icon: 'cover-image', category: 'oribi', supports: { align: ['full'], html: false }, attributes: { align: { type: 'string', default: 'full' }, label: { type: 'string', default: '' }, title: { type: 'string', default: '' }, highlightWord: { type: 'string', default: '' }, description: { type: 'string', default: '' }, primaryBtnText: { type: 'string', default: 'Get in Touch' }, primaryBtnUrl: { type: 'string', default: '/contact' }, secondaryBtnText: { type: 'string', default: '' }, secondaryBtnUrl: { type: 'string', default: '' }, stat1Value: { type: 'string', default: '' }, stat1Label: { type: 'string', default: '' }, stat2Value: { type: 'string', default: '' }, stat2Label: { type: 'string', default: '' }, svcLaptop1: { type: 'string', default: 'Data Backup' }, svcLaptop2: { type: 'string', default: 'Endpoint Security' }, svcLaptop3: { type: 'string', default: 'Patch Management' }, svcCloud1: { type: 'string', default: 'Email Protection' }, svcCloud2: { type: 'string', default: 'License Management' }, svcCloud3: { type: 'string', default: 'Cloud Backup' }, svcDesktop1: { type: 'string', default: 'Network Monitoring' }, svcDesktop2: { type: 'string', default: 'Threat Detection' }, svcDesktop3: { type: 'string', default: 'Cloud Management' }, svcPhone1: { type: 'string', default: 'Mobile Security' }, svcPhone2: { type: 'string', default: 'Data Encryption' }, }, edit: function (props) { var a = props.attributes, s = props.setAttributes; return el(Frag, null, el(IC, null, el(PB, { title: 'Highlight' }, el(TC, { label: 'Word to highlight in title', value: a.highlightWord, onChange: function (v) { s({ highlightWord: v }); } }) ), el(PB, { title: 'Primary Button' }, el(TC, { label: 'URL', value: a.primaryBtnUrl, onChange: function (v) { s({ primaryBtnUrl: v }); } }) ), el(PB, { title: 'Secondary Button', initialOpen: false }, el(TC, { label: 'URL', value: a.secondaryBtnUrl, onChange: function (v) { s({ secondaryBtnUrl: v }); } }) ) ), el('section', { className: 'hero' }, el('div', { className: 'container hero-inner' }, el('div', { className: 'hero-content' }, el(RT, { tagName: 'span', className: 'hero-label', value: a.label, onChange: function (v) { s({ label: v }); }, placeholder: '\u25CF Label text', allowedFormats: [] }), el(RT, { tagName: 'h1', className: 'hero-title', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Hero title...' }), el(RT, { tagName: 'p', className: 'hero-description', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }), el('div', { className: 'btn-group' }, el(RT, { tagName: 'span', className: 'btn btn-primary btn-lg', value: a.primaryBtnText, onChange: function (v) { s({ primaryBtnText: v }); }, placeholder: 'Button', allowedFormats: [] }), el(RT, { tagName: 'span', className: 'btn btn-ghost btn-lg', value: a.secondaryBtnText, onChange: function (v) { s({ secondaryBtnText: v }); }, placeholder: 'Secondary button', allowedFormats: [] }) ), el('div', { className: 'hero-stats' }, el('div', null, el(RT, { tagName: 'div', className: 'hero-stat-value', value: a.stat1Value, onChange: function (v) { s({ stat1Value: v }); }, placeholder: '\u2014', allowedFormats: [] }), el(RT, { tagName: 'div', className: 'hero-stat-label', value: a.stat1Label, onChange: function (v) { s({ stat1Label: v }); }, placeholder: 'Stat label', allowedFormats: [] }) ), el('div', null, el(RT, { tagName: 'div', className: 'hero-stat-value', value: a.stat2Value, onChange: function (v) { s({ stat2Value: v }); }, placeholder: '\u2014', allowedFormats: [] }), el(RT, { tagName: 'div', className: 'hero-stat-label', value: a.stat2Label, onChange: function (v) { s({ stat2Label: v }); }, placeholder: 'Stat label', allowedFormats: [] }) ) ) ), el('div', { className: 'hero-visual' }, el('div', { className: 'hero-devices' }, el('div', { className: 'hero-device hero-device--laptop', style: { opacity: 1 } }, el('div', { className: 'hero-device__frame' }, el('div', { className: 'hero-device__screen' }, el('div', { className: 'hero-device__screen-content' }, el('div', { className: 'hero-device__app-bars' }, el('div'), el('div'), el('div'), el('div')) ) ), el('div', { className: 'hero-device__base' }) ), el('ul', { className: 'hero-device__services' }, el('li', { className: 'svc', style: { opacity: 1 } }, el('span', { className: 'svc__dot' }), ' Data Backup'), el('li', { className: 'svc', style: { opacity: 1 } }, el('span', { className: 'svc__dot' }), ' Endpoint Security'), el('li', { className: 'svc', style: { opacity: 1 } }, el('span', { className: 'svc__dot' }), ' Patch Management') ) ), el('div', { className: 'hero-device hero-device--cloud', style: { opacity: 1 } }, el('div', { className: 'hero-device__frame' }, el('div', { className: 'hero-device__cloud-icon' }, el('span', { className: 'hero-device__cloud-label' }, '365') ) ), el('ul', { className: 'hero-device__services' }, el('li', { className: 'svc', style: { opacity: 1 } }, el('span', { className: 'svc__dot' }), ' Email Protection'), el('li', { className: 'svc', style: { opacity: 1 } }, el('span', { className: 'svc__dot' }), ' License Management'), el('li', { className: 'svc', style: { opacity: 1 } }, el('span', { className: 'svc__dot' }), ' Cloud Backup') ) ), el('div', { className: 'hero-device hero-device--desktop', style: { opacity: 1 } }, el('div', { className: 'hero-device__frame' }, el('div', { className: 'hero-device__screen' }, el('div', { className: 'hero-device__screen-content' }, el('div', { className: 'hero-device__dash-row' }, el('div', { className: 'hero-device__dash-card' }), el('div', { className: 'hero-device__dash-card' })), el('div', { className: 'hero-device__dash-bar' }), el('div', { className: 'hero-device__dash-bar hero-device__dash-bar--short' }) ) ), el('div', { className: 'hero-device__stand' }), el('div', { className: 'hero-device__stand-base' }) ), el('ul', { className: 'hero-device__services' }, el('li', { className: 'svc', style: { opacity: 1 } }, el('span', { className: 'svc__dot' }), ' Network Monitoring'), el('li', { className: 'svc', style: { opacity: 1 } }, el('span', { className: 'svc__dot' }), ' Threat Detection'), el('li', { className: 'svc', style: { opacity: 1 } }, el('span', { className: 'svc__dot' }), ' Cloud Management') ) ), el('div', { className: 'hero-device hero-device--phone', style: { opacity: 1 } }, el('div', { className: 'hero-device__frame' }, el('div', { className: 'hero-device__screen' }, el('div', { className: 'hero-device__screen-content' }, el('div', { className: 'hero-device__notif' }, el('span', { className: 'hero-device__notif-icon' }, '\u2713'), el('span', { className: 'hero-device__notif-text' }, 'Secure') ) ) ) ), el('ul', { className: 'hero-device__services' }, el('li', { className: 'svc', style: { opacity: 1 } }, el('span', { className: 'svc__dot' }), ' Mobile Security'), el('li', { className: 'svc', style: { opacity: 1 } }, el('span', { className: 'svc__dot' }), ' Data Encryption') ) ) ) ) ) ) ); }, save: function () { return null; } }); /* 2. PAGE HERO ────────────────────────────────────────────────────────── */ reg('oribi/page-hero', { title: 'Oribi Page Hero', icon: 'flag', category: 'oribi', supports: { align: ['full'], html: false }, attributes: { align: { type: 'string', default: 'full' }, label: { type: 'string', default: '' }, title: { type: 'string', default: '' }, description: { type: 'string', default: '' }, }, edit: function (props) { var a = props.attributes, s = props.setAttributes; return el(Frag, null, el(IC, null, el(PB, { title: 'Settings' }, el(TC, { label: 'Label (optional)', value: a.label, onChange: function (v) { s({ label: v }); } }) ) ), el('section', { className: 'page-hero' }, el('div', { className: 'container' }, a.label ? el('span', { className: 'hero-label' }, a.label) : null, el(RT, { tagName: 'h1', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Page title...' }), el(RT, { tagName: 'p', className: 'lead', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }) ) ) ); }, save: function () { return null; } }); /* 5. CTA BANNER ───────────────────────────────────────────────────────── */ reg('oribi/cta-banner', { title: 'Oribi CTA Banner', icon: 'megaphone', category: 'oribi', supports: { align: ['full'], html: false }, attributes: { align: { type: 'string', default: 'full' }, heading: { type: 'string', default: '' }, text: { type: 'string', default: '' }, btnText: { type: 'string', default: '' }, btnUrl: { type: 'string', default: '' }, }, edit: function (props) { var a = props.attributes, s = props.setAttributes; return el(Frag, null, el(IC, null, el(PB, { title: 'Button' }, el(TC, { label: 'URL', value: a.btnUrl, onChange: function (v) { s({ btnUrl: v }); } }) ) ), el('section', { className: 'cta-banner' }, el('div', { className: 'container text-center' }, el(RT, { tagName: 'h2', value: a.heading, onChange: function (v) { s({ heading: v }); }, placeholder: 'CTA heading...' }), el(RT, { tagName: 'p', value: a.text, onChange: function (v) { s({ text: v }); }, placeholder: 'CTA text...' }), el(RT, { tagName: 'span', className: 'btn btn-primary btn-lg', style: { background: '#fff', color: 'var(--color-primary)' }, value: a.btnText, onChange: function (v) { s({ btnText: v }); }, placeholder: 'Button text...' }) ) ) ); }, save: function () { return null; } }); /* 6. INTRO SECTION ────────────────────────────────────────────────────── */ reg('oribi/intro-section', { title: 'Oribi Intro Section', icon: 'id', category: 'oribi', supports: { align: ['full'], html: false }, attributes: { align: { type: 'string', default: 'full' }, variant: { type: 'string', default: 'normal' }, label: { type: 'string', default: '' }, heading: { type: 'string', default: '' }, description: { type: 'string', default: '' }, visual: { type: 'string', default: '' }, reversed: { type: 'boolean', default: false }, cloudAnim: { type: 'boolean', default: false }, imgId: { type: 'number', default: 0 }, imgUrl: { type: 'string', default: '' }, imgAlt: { type: 'string', default: '' }, imgWidth: { type: 'number', default: 280 }, }, edit: function (props) { var a = props.attributes, s = props.setAttributes; var imgW = a.imgWidth || 280; var animHtml = null; if (a.cloudAnim) { animHtml = el('div', { className: 'ds-anim-container' }, el('div', { className: 'ds-tv' }, el('i', { className: 'fas fa-tv', 'aria-hidden': 'true' }), el('div', { className: 'ds-tv-screen' }) ), el('div', { className: 'ds-line' }, el('div', { className: 'ds-packet ds-packet-1' }), el('div', { className: 'ds-packet ds-packet-2' }), el('div', { className: 'ds-packet ds-packet-3' }) ), el('div', { className: 'ds-cloud' }, el('i', { className: 'fas fa-cloud', 'aria-hidden': 'true' }) ) ); } var visualContent = a.cloudAnim ? animHtml : (a.imgUrl ? el('img', { src: a.imgUrl, style: { width: imgW + 'px', maxWidth: '100%', height: 'auto', borderRadius: '8px', objectFit: 'contain', display: 'block' } }) : (a.visual || '\uD83D\uDCBB')); return el(Frag, null, el(IC, null, el(PB, { title: 'Settings' }, el(SC, { label: 'Background', value: a.variant, options: [ { label: 'Normal', value: 'normal' }, { label: 'Alternate', value: 'alt' } ], onChange: function (v) { s({ variant: v }); } }), el(TC, { label: 'Label', value: a.label, onChange: function (v) { s({ label: v }); } }), el(TC, { label: 'Visual (emoji or text)', value: a.visual, onChange: function (v) { s({ visual: v }); } }), el(TG, { label: 'Reversed layout', checked: a.reversed, onChange: function (v) { s({ reversed: v }); } }), el(TG, { label: 'Cloud Server Animation', checked: a.cloudAnim, onChange: function (v) { s({ cloudAnim: v }); } }) ), el(PB, { title: 'Visual Image', initialOpen: false }, el(MUC, null, el(MU, { onSelect: function (media) { s({ imgId: media.id, imgUrl: media.url, imgAlt: media.alt || '' }); }, allowedTypes: ['image'], value: a.imgId || 0, render: function (ref) { return el(Frag, null, a.imgUrl ? el('div', { style: { marginBottom: '8px' } }, el('img', { src: a.imgUrl, style: { maxWidth: '100%', height: 'auto', borderRadius: '4px', display: 'block', marginBottom: '6px' } }), el(Btn, { variant: 'link', isDestructive: true, onClick: function () { s({ imgId: 0, imgUrl: '', imgAlt: '' }); } }, 'Remove image') ) : null, el(Btn, { onClick: ref.open, variant: 'secondary', __next40pxDefaultSize: true }, a.imgUrl ? 'Replace image' : 'Select from Media Library') ); } }) ), a.imgUrl ? el(RC, { label: 'Width (px)', value: imgW, min: 50, max: 420, step: 4, onChange: function (v) { s({ imgWidth: v }); } }) : null ) ), el('section', { className: a.variant === 'alt' ? 'section section-alt' : 'section' }, el('div', { className: 'container' }, el('div', { className: 'about-intro', style: a.reversed ? { direction: 'rtl' } : {} }, el('div', { style: a.reversed ? { direction: 'ltr' } : {} }, a.label ? el('span', { className: 'section-label' }, a.label) : null, el(RT, { tagName: 'h2', style: { marginBottom: '1.5rem' }, value: a.heading, onChange: function (v) { s({ heading: v }); }, placeholder: 'Heading...' }), el(RT, { tagName: 'p', className: 'lead', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }) ), el('div', { className: 'about-intro-visual' + (a.cloudAnim ? ' has-cloud-anim' : (a.imgUrl ? ' has-img' : '')), style: a.reversed ? { direction: 'ltr' } : {} }, visualContent) ) ) ) ); }, save: function () { return null; } }); /* 8. CONTACT SECTION ──────────────────────────────────────────────────── */ reg('oribi/contact-section', { title: 'Oribi Contact', icon: 'email', category: 'oribi', supports: { align: ['full'], html: false }, attributes: { align: { type: 'string', default: 'full' }, heading: { type: 'string', default: "Let's Talk" }, lead: { type: 'string', default: '' }, email: { type: 'string', default: 'solutions@oribi-tech.com' }, supportUrl: { type: 'string', default: '' }, portalUrl: { type: 'string', default: '' }, location: { type: 'string', default: 'Saratoga Springs, Upstate New York' }, formHeading: { type: 'string', default: 'Want to Learn More?' }, }, edit: function (props) { var a = props.attributes, s = props.setAttributes; return el(Frag, null, el(IC, null, el(PB, { title: 'Contact Settings' }, el(TC, { label: 'Email', value: a.email, onChange: function (v) { s({ email: v }); } }), el(TC, { label: 'Support URL', value: a.supportUrl, onChange: function (v) { s({ supportUrl: v }); } }), el(TC, { label: 'Portal URL', value: a.portalUrl, onChange: function (v) { s({ portalUrl: v }); } }), el(TC, { label: 'Location', value: a.location, onChange: function (v) { s({ location: v }); } }), el(TC, { label: 'Form Heading', value: a.formHeading, onChange: function (v) { s({ formHeading: v }); } }) ) ), el('section', { className: 'section' }, el('div', { className: 'container' }, el('div', { className: 'contact-layout' }, el('div', { className: 'contact-info' }, el(RT, { tagName: 'h2', value: a.heading, onChange: function (v) { s({ heading: v }); }, placeholder: 'Heading...' }), el(RT, { tagName: 'p', className: 'lead', value: a.lead, onChange: function (v) { s({ lead: v }); }, placeholder: 'Lead text...' }), el('div', { className: 'contact-method' }, el('div', { className: 'contact-method-icon' }, '\uD83D\uDCE7'), el('div', null, el('div', { className: 'contact-method-label' }, 'Email Us'), el('div', { className: 'contact-method-value' }, a.email)) ), el('div', { className: 'contact-method' }, el('div', { className: 'contact-method-icon' }, '\uD83C\uDFAB'), el('div', null, el('div', { className: 'contact-method-label' }, 'Support'), el('div', { className: 'contact-method-value' }, 'Open a Support Ticket')) ), el('div', { className: 'contact-method' }, el('div', { className: 'contact-method-icon' }, '\uD83C\uDF0E'), el('div', null, el('div', { className: 'contact-method-label' }, 'Client Portal'), el('div', { className: 'contact-method-value' }, 'portal.oribi-tech.com')) ), el('div', { className: 'contact-method' }, el('div', { className: 'contact-method-icon' }, '\uD83D\uDCCD'), el('div', null, el('div', { className: 'contact-method-label' }, 'Location'), el('div', { className: 'contact-method-value' }, a.location)) ) ), el('div', { className: 'contact-form-wrap' }, el('h3', { style: { marginBottom: '1.5rem' } }, a.formHeading), el('div', { style: { padding: '2rem', background: 'var(--color-bg-alt)', borderRadius: 'var(--radius-md)', textAlign: 'center', color: 'var(--color-text-muted)' } }, 'Contact form renders on the live site' ) ) ) ) ) ); }, save: function () { return null; } }); /* ═══════════════════════════════════════════════════════════════════════ CHILD BLOCKS (each renders one item inside a parent) ═══════════════════════════════════════════════════════════════════════ */ /* ── Feature Card ─────────────────────────────────────────────────────── */ reg('oribi/feature-card', { title: 'Feature Card', icon: 'screenoptions', category: 'oribi', parent: ['oribi/feature-section'], supports: { html: false, reusable: false }, attributes: Object.assign({}, { icon: { type: 'string', default: '' }, iconType: { type: 'string', default: 'emoji' }, faIcon: { type: 'string', default: '' }, title: { type: 'string', default: '' }, description: { type: 'string', default: '' }, url: { type: 'string', default: '' }, centered: { type: 'boolean', default: false } }, CARD_IMAGE_ATTRS), edit: function (props) { var a = props.attributes, s = props.setAttributes; var imgPos = a.imgPosition || 'top'; var imgPrev = cardImagePreview(a); var centeredStyle = a.centered ? { marginInline: 'auto' } : {}; var cardPreview; if (a.imgUrl && imgPos === 'left') { cardPreview = el('div', { className: 'oribi-card img-left' }, imgPrev, el('div', { className: 'oribi-card-body' }, el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Card title...' }), el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Card description...' }) ) ); } else if (a.imgUrl && imgPos === 'background') { cardPreview = el('div', { className: 'oribi-card img-bg', style: { backgroundImage: 'url(' + a.imgUrl + ')', backgroundSize: 'cover', backgroundPosition: 'center', color: '#fff' } }, el('div', { className: 'oribi-card-body' }, iconPreview(a, 'feature-icon', centeredStyle), el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Card title...' }), el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Card description...' }) ) ); } else { var showIconPrev = !a.imgUrl || imgPos !== 'replace-icon'; cardPreview = el('div', { className: 'oribi-card' + (a.centered ? ' text-center' : '') + (a.imgUrl ? ' img-' + imgPos : '') }, a.imgUrl && (imgPos === 'top' || imgPos === 'replace-icon') ? imgPrev : null, showIconPrev ? iconPreview(a, 'feature-icon', centeredStyle) : null, el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Card title...' }), el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Card description...' }) ); } return el(Frag, null, el(IC, null, el(PB, { title: 'Card Settings' }, iconControls(a, s), el(TC, { label: 'URL (optional)', value: a.url || '', onChange: function (v) { s({ url: v }); } }), el(TG, { label: 'Centered', checked: !!a.centered, onChange: function (v) { s({ centered: v }); } }) ), cardImageControls(a, s) ), cardPreview ); }, save: function () { return null; } }); /* ── Value Card ───────────────────────────────────────────────────────── */ reg('oribi/value-card', { title: 'Value Card', icon: 'heart', category: 'oribi', parent: ['oribi/value-section'], supports: { html: false, reusable: false }, attributes: Object.assign({}, { icon: { type: 'string', default: '' }, iconType: { type: 'string', default: 'emoji' }, faIcon: { type: 'string', default: '' }, title: { type: 'string', default: '' }, description: { type: 'string', default: '' } }, CARD_IMAGE_ATTRS), edit: function (props) { var a = props.attributes, s = props.setAttributes; var imgPos = a.imgPosition || 'top'; var imgPrev = cardImagePreview(a); var showIconPrev = !a.imgUrl || imgPos !== 'replace-icon'; var body; if (a.imgUrl && imgPos === 'left') { body = el('div', { className: 'oribi-card value-card img-left' }, imgPrev, el('div', { className: 'oribi-card-body' }, el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Title...' }), el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }) ) ); } else if (a.imgUrl && imgPos === 'background') { body = el('div', { className: 'oribi-card value-card img-bg', style: { backgroundImage: 'url(' + a.imgUrl + ')', backgroundSize: 'cover', backgroundPosition: 'center', color: '#fff' } }, el('div', { className: 'oribi-card-body' }, showIconPrev ? iconPreview(a, 'value-icon') : null, el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Title...' }), el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }) ) ); } else { body = el('div', { className: 'oribi-card value-card' + (a.imgUrl ? ' img-' + imgPos : '') }, a.imgUrl && (imgPos === 'top' || imgPos === 'replace-icon') ? imgPrev : null, showIconPrev ? iconPreview(a, 'value-icon') : null, el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Title...' }), el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }) ); } return el(Frag, null, el(IC, null, el(PB, { title: 'Card Settings' }, iconControls(a, s) ), cardImageControls(a, s) ), body ); }, save: function () { return null; } }); /* ── Addon Card ───────────────────────────────────────────────────────── */ reg('oribi/addon-card', { title: 'Addon Card', icon: 'plus-alt2', category: 'oribi', parent: ['oribi/addon-section'], supports: { html: false, reusable: false }, attributes: Object.assign({}, { icon: { type: 'string', default: '' }, iconType: { type: 'string', default: 'emoji' }, faIcon: { type: 'string', default: '' }, title: { type: 'string', default: '' }, description: { type: 'string', default: '' }, tag: { type: 'string', default: '' } }, CARD_IMAGE_ATTRS), edit: function (props) { var a = props.attributes, s = props.setAttributes; var imgPos = a.imgPosition || 'top'; var imgPrev = cardImagePreview(a); var showIconPrev = !a.imgUrl || imgPos !== 'replace-icon'; var body; if (a.imgUrl && imgPos === 'left') { body = el('div', { className: 'oribi-card img-left' }, imgPrev, el('div', { className: 'oribi-card-body' }, a.tag ? el('span', { className: 'addon-tag' }, a.tag) : null, el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Title...' }), el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }) ) ); } else if (a.imgUrl && imgPos === 'background') { body = el('div', { className: 'oribi-card img-bg', style: { backgroundImage: 'url(' + a.imgUrl + ')', backgroundSize: 'cover', backgroundPosition: 'center', color: '#fff' } }, el('div', { className: 'oribi-card-body' }, showIconPrev ? iconPreview(a, 'feature-icon') : null, a.tag ? el('span', { className: 'addon-tag' }, a.tag) : null, el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Title...' }), el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }) ) ); } else { body = el('div', { className: 'oribi-card' + (a.imgUrl ? ' img-' + imgPos : '') }, a.imgUrl && (imgPos === 'top' || imgPos === 'replace-icon') ? imgPrev : null, showIconPrev ? iconPreview(a, 'feature-icon') : null, a.tag ? el('span', { className: 'addon-tag' }, a.tag) : null, el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Title...' }), el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }) ); } return el(Frag, null, el(IC, null, el(PB, { title: 'Card Settings' }, iconControls(a, s), el(TC, { label: 'Tag / Badge (optional)', value: a.tag || '', onChange: function (v) { s({ tag: v }); } }) ), cardImageControls(a, s) ), body ); }, save: function () { return null; } }); /* ── Image Card ───────────────────────────────────────────────────────── */ reg('oribi/image-card', { title: 'Image Card', icon: 'format-image', category: 'oribi', parent: ['oribi/image-section'], supports: { html: false, reusable: false }, attributes: Object.assign({}, { icon: { type: 'string', default: '' }, iconType: { type: 'string', default: 'emoji' }, faIcon: { type: 'string', default: '' }, title: { type: 'string', default: '' }, description: { type: 'string', default: '' }, url: { type: 'string', default: '' } }, CARD_IMAGE_ATTRS), edit: function (props) { var a = props.attributes, s = props.setAttributes; var imgPos = a.imgPosition || 'top'; var imgPrev = cardImagePreview(a); var showIconPrev = !a.imgUrl || imgPos !== 'replace-icon'; return el(Frag, null, el(IC, null, el(PB, { title: 'Card Settings' }, iconControls(a, s), el(TC, { label: 'URL (optional)', value: a.url || '', onChange: function (v) { s({ url: v }); } }) ), cardImageControls(a, s) ), el('div', { className: 'oribi-card image-card' + (a.imgUrl ? ' img-' + imgPos : '') }, a.imgUrl && (imgPos === 'top' || imgPos === 'replace-icon') ? imgPrev : null, showIconPrev ? iconPreview(a, 'feature-icon') : null, el('div', { className: 'oribi-card-body' }, el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Title...' }), el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }) ) ) ); }, save: function () { return null; } }); /* ── Stat Card ────────────────────────────────────────────────────────── */ reg('oribi/stat-card', { title: 'Stat Card', icon: 'chart-bar', category: 'oribi', parent: ['oribi/stat-section'], supports: { html: false, reusable: false }, attributes: Object.assign({}, { icon: { type: 'string', default: '' }, iconType: { type: 'string', default: 'emoji' }, faIcon: { type: 'string', default: '' }, value: { type: 'string', default: '' }, label: { type: 'string', default: '' }, description: { type: 'string', default: '' } }, CARD_IMAGE_ATTRS), edit: function (props) { var a = props.attributes, s = props.setAttributes; var imgPrev = cardImagePreview(a); var imgPos = a.imgPosition || 'top'; var body; if (a.imgUrl && imgPos === 'background') { body = el('div', { className: 'oribi-card stat-card img-bg', style: { backgroundImage: 'url(' + a.imgUrl + ')', backgroundSize: 'cover', backgroundPosition: 'center', color: '#fff' } }, el('div', { className: 'oribi-card-body' }, iconPreview(a, 'feature-icon'), el(RT, { tagName: 'div', className: 'stat-value', value: a.value, onChange: function (v) { s({ value: v }); }, placeholder: '99.9%' }), el(RT, { tagName: 'div', className: 'stat-label', value: a.label, onChange: function (v) { s({ label: v }); }, placeholder: 'Label...' }), el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description (optional)...' }) ) ); } else { body = el('div', { className: 'oribi-card stat-card' + (a.imgUrl ? ' img-' + imgPos : '') }, (a.imgUrl && imgPos !== 'replace-icon') ? imgPrev : null, iconPreview(a, 'feature-icon'), el(RT, { tagName: 'div', className: 'stat-value', value: a.value, onChange: function (v) { s({ value: v }); }, placeholder: '99.9%' }), el(RT, { tagName: 'div', className: 'stat-label', value: a.label, onChange: function (v) { s({ label: v }); }, placeholder: 'Label...' }), el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description (optional)...' }) ); } return el(Frag, null, el(IC, null, el(PB, { title: 'Card Settings' }, iconControls(a, s) ), cardImageControls(a, s) ), body ); }, save: function () { return null; } }); /* ── Link Card ────────────────────────────────────────────────────────── */ reg('oribi/link-card', { title: 'Link Card', icon: 'admin-links', category: 'oribi', parent: ['oribi/link-section'], supports: { html: false, reusable: false }, attributes: Object.assign({}, { icon: { type: 'string', default: '' }, iconType: { type: 'string', default: 'emoji' }, faIcon: { type: 'string', default: '' }, title: { type: 'string', default: '' }, description: { type: 'string', default: '' }, linkText: { type: 'string', default: '' }, linkUrl: { type: 'string', default: '' } }, CARD_IMAGE_ATTRS), edit: function (props) { var a = props.attributes, s = props.setAttributes; var imgPos = a.imgPosition || 'top'; var imgPrev = cardImagePreview(a); var showIconPrev = !a.imgUrl || imgPos !== 'replace-icon'; var body; if (a.imgUrl && imgPos === 'left') { body = el('div', { className: 'oribi-card link-card img-left' }, imgPrev, el('div', { className: 'oribi-card-body' }, showIconPrev ? iconPreview(a, 'feature-icon') : null, el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Title...' }), el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }), el(RT, { tagName: 'span', className: 'link-card-cta', value: a.linkText, onChange: function (v) { s({ linkText: v }); }, placeholder: 'Link text → ...' }) ) ); } else if (a.imgUrl && imgPos === 'background') { body = el('div', { className: 'oribi-card link-card img-bg', style: { backgroundImage: 'url(' + a.imgUrl + ')', backgroundSize: 'cover', backgroundPosition: 'center', color: '#fff' } }, el('div', { className: 'oribi-card-body' }, showIconPrev ? iconPreview(a, 'feature-icon') : null, el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Title...' }), el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }), el(RT, { tagName: 'span', className: 'link-card-cta', value: a.linkText, onChange: function (v) { s({ linkText: v }); }, placeholder: 'Link text → ...' }) ) ); } else { body = el('div', { className: 'oribi-card link-card' + (a.imgUrl ? ' img-' + imgPos : '') }, (a.imgUrl && (imgPos === 'top' || imgPos === 'replace-icon')) ? imgPrev : null, showIconPrev ? iconPreview(a, 'feature-icon') : null, el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Title...' }), el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }), el(RT, { tagName: 'span', className: 'link-card-cta', value: a.linkText, onChange: function (v) { s({ linkText: v }); }, placeholder: 'Link text → ...' }) ); } return el(Frag, null, el(IC, null, el(PB, { title: 'Card Settings' }, iconControls(a, s), el(TC, { label: 'Link URL', value: a.linkUrl || '', onChange: function (v) { s({ linkUrl: v }); } }) ), cardImageControls(a, s) ), body ); }, save: function () { return null; } }); /* ── Pricing Card ─────────────────────────────────────────────────────── */ reg('oribi/pricing-card', { title: 'Pricing Card', icon: 'money-alt', category: 'oribi', parent: ['oribi/pricing-section'], supports: { html: false, reusable: false }, attributes: { icon: { type: 'string', default: '' }, iconType: { type: 'string', default: 'emoji' }, faIcon: { type: 'string', default: '' }, name: { type: 'string', default: '' }, tagline: { type: 'string', default: '' }, price: { type: 'string', default: '' }, pricePer: { type: 'string', default: '' }, features: { type: 'array', default: [] }, btnText: { type: 'string', default: 'Get Started' }, btnUrl: { type: 'string', default: '/contact' }, featured: { type: 'boolean', default: false }, badge: { type: 'string', default: '' }, imgId: { type: 'number', default: 0 }, imgUrl: { type: 'string', default: '' }, imgAlt: { type: 'string', default: '' }, imgWidth: { type: 'number', default: 80 }, }, edit: function (props) { var a = props.attributes, s = props.setAttributes; var features = a.features || []; var imgW = a.imgWidth || 80; return el(Frag, null, el(IC, null, el(PB, { title: 'Card Settings' }, iconControls(a, s), el(TC, { label: 'Button URL', value: a.btnUrl, onChange: function (v) { s({ btnUrl: v }); } }), el(TG, { label: 'Featured', checked: !!a.featured, onChange: function (v) { s({ featured: v }); } }), a.featured ? el(TC, { label: 'Badge Text', value: a.badge, onChange: function (v) { s({ badge: v }); } }) : null ), el(PB, { title: 'Card Image', initialOpen: false }, el(MUC, null, el(MU, { onSelect: function (media) { s({ imgId: media.id, imgUrl: media.url, imgAlt: media.alt || '' }); }, allowedTypes: ['image'], value: a.imgId || 0, render: function (ref) { return el(Frag, null, a.imgUrl ? el('div', { style: { marginBottom: '8px' } }, el('img', { src: a.imgUrl, style: { maxWidth: '100%', height: 'auto', borderRadius: '4px', display: 'block', marginBottom: '6px' } }), el(Btn, { variant: 'link', isDestructive: true, onClick: function () { s({ imgId: 0, imgUrl: '', imgAlt: '' }); } }, 'Remove image') ) : null, el(Btn, { onClick: ref.open, variant: 'secondary', __next40pxDefaultSize: true }, a.imgUrl ? 'Replace image' : 'Select from Media Library') ); } }) ), a.imgUrl ? el(RC, { label: 'Width (px)', value: imgW, min: 20, max: 400, step: 4, onChange: function (v) { s({ imgWidth: v }); } }) : null ) ), el('div', { className: 'pricing-card' + (a.featured ? ' featured' : '') }, a.featured && a.badge ? el(RT, { tagName: 'span', className: 'pricing-badge', value: a.badge, onChange: function (v) { s({ badge: v }); }, placeholder: 'Badge...' }) : null, a.imgUrl ? el('div', { style: { textAlign: 'center', marginBottom: '1.25rem' } }, el('img', { src: a.imgUrl, style: { width: imgW + 'px', maxWidth: '100%', height: 'auto', borderRadius: '4px', objectFit: 'contain' } }) ) : null, iconPreview(a, 'feature-icon', { marginInline: 'auto' }), el(RT, { tagName: 'div', className: 'pricing-name', value: a.name, onChange: function (v) { s({ name: v }); }, placeholder: 'Plan name...' }), el(RT, { tagName: 'p', className: 'pricing-tagline', value: a.tagline, onChange: function (v) { s({ tagline: v }); }, placeholder: 'Tagline...' }), a.price || a.pricePer ? el('div', { className: 'pricing-price' }, el(RT, { tagName: 'div', className: 'pricing-amount', value: a.price || '', onChange: function (v) { s({ price: v }); }, placeholder: '$0' }), el(RT, { tagName: 'div', className: 'pricing-per', value: a.pricePer || '', onChange: function (v) { s({ pricePer: v }); }, placeholder: 'per screen / month' }) ) : el('div', { className: 'pricing-price' }, el(RT, { tagName: 'div', className: 'pricing-amount', value: '', onChange: function (v) { s({ price: v }); }, placeholder: '$0' }), el(RT, { tagName: 'div', className: 'pricing-per', value: '', onChange: function (v) { s({ pricePer: v }); }, placeholder: 'per screen / month' }) ), el('ul', { className: 'pricing-features' }, features.map(function (f, fi) { return el('li', { key: fi, style: { display: 'flex', alignItems: 'center', gap: '4px' } }, el('span', { className: 'pricing-check' }, '\u2713'), el(RT, { tagName: 'span', style: { flex: 1, minWidth: 0 }, value: f, onChange: function (v) { s({ features: arrSet(features, fi, v) }); }, placeholder: 'Feature...' }), el(Btn, { isSmall: true, isDestructive: true, style: { minWidth: '20px', padding: 0, height: '20px', flexShrink: 0 }, onClick: function () { s({ features: arrRm(features, fi) }); } }, '\u2715') ); }), el('li', { style: { listStyle: 'none', marginTop: '4px' } }, el(Btn, { isSmall: true, variant: 'secondary', onClick: function () { s({ features: arrAdd(features, '') }); } }, '+ Feature') ) ), el(RT, { tagName: 'span', className: 'btn ' + (a.featured ? 'btn-primary' : 'btn-outline'), style: { width: '100%', justifyContent: 'center', cursor: 'text' }, value: a.btnText || '', onChange: function (v) { s({ btnText: v }); }, placeholder: 'Button text...' }) ) ); }, save: function () { return null; } }); /* ── Platform Row ─────────────────────────────────────────────────────── */ reg('oribi/platform-row', { title: 'Platform Row', icon: 'slides', category: 'oribi', parent: ['oribi/platform-section'], supports: { html: false, reusable: false }, attributes: { heading: { type: 'string', default: '' }, description: { type: 'string', default: '' }, btnText: { type: 'string', default: 'Learn More' }, btnUrl: { type: 'string', default: '' }, visual: { type: 'string', default: '' }, reversed: { type: 'boolean', default: false }, imgId: { type: 'number', default: 0 }, imgUrl: { type: 'string', default: '' }, imgAlt: { type: 'string', default: '' }, imgWidth: { type: 'number', default: 300 }, isDashboard: { type: 'boolean', default: false }, deviceAnim: { type: 'boolean', default: false }, tvStick: { type: 'boolean', default: false }, cameraAnim: { type: 'boolean', default: false }, neverGoesDark: { type: 'boolean', default: false }, brandedAnim: { type: 'boolean', default: false }, galleryIds: { type: 'array', default: [] }, }, edit: function (props) { var a = props.attributes, s = props.setAttributes; var imgW = a.imgWidth || 300; /* ── Animation HTML strings (mirror PHP render, used via dangerouslySetInnerHTML) ── */ var DA_SCREEN = '