Files
OTSSigns-Website/theme/blocks/editor.js
Matt Batchelder d689a06640 Enhance platform row block with new animation options and gallery support
- Added new attributes for dashboard, device, TV stick, camera, never goes dark, and branded animations in the platform row block.
- Implemented HTML structures for various animations including dashboard, device, TV stick, and branded displays.
- Integrated a media uploader for selecting images for a TV slideshow.
- Updated the editor controls to manage new animation settings and gallery images.

Update enqueue scripts for new animation functionalities

- Enqueued scripts for gallery TV slideshow and video editor timeline animator.

Revise theme color palette for improved aesthetics

- Changed primary color scheme to a new vibrant palette for light and dark modes.

Adjust button styles to reflect new primary color

- Updated button background color to match the new primary color in theme settings.

Modify theme JSON to align with new color definitions

- Updated color definitions in theme.json to reflect the new primary color scheme for both light and dark modes.
2026-02-22 11:14:51 -05:00

2056 lines
129 KiB
JavaScript

/**
* 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 },
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 visualContent = 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(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.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 = '<div class="da-screen"><div class="da-promo"><div class="da-promo__top"><span class="da-promo__dot"></span><span class="da-promo__brand"></span></div><div class="da-promo__hero"></div><div class="da-promo__row"><span class="da-promo__line da-promo__line--lg"></span><span class="da-promo__line da-promo__line--sm"></span></div><div class="da-promo__row"><span class="da-promo__line da-promo__line--md"></span><span class="da-promo__line da-promo__line--xs"></span></div><div class="da-promo__ticker"><span class="da-promo__chip"></span><span class="da-promo__chip"></span><span class="da-promo__chip"></span></div></div></div>';
var DA_HTML = '<div class="da-stage" aria-hidden="true">' +
'<div class="da-device da-tablet"><div class="da-body">' + DA_SCREEN + '</div><span class="da-label">Tablet</span></div>' +
'<div class="da-device da-monitor-sm"><div class="da-body">' + DA_SCREEN + '</div><div class="da-stand"><div class="da-stem"></div><div class="da-base"></div></div><span class="da-label">Small Monitor</span></div>' +
'<div class="da-device da-monitor-lg"><div class="da-body">' + DA_SCREEN + '</div><div class="da-stand"><div class="da-stem"></div><div class="da-base"></div></div><span class="da-label">Large Monitor</span></div>' +
'<div class="da-device da-tv"><div class="da-body">' + DA_SCREEN + '</div><div class="da-feet"><div class="da-foot"></div><div class="da-foot"></div></div><span class="da-label">TV</span></div>' +
'<div class="da-device da-projector"><div class="da-proj-layout"><div class="da-proj-body"><div class="da-lens"></div></div><div class="da-beam"></div><div class="da-proj-screen">' + DA_SCREEN + '</div></div><span class="da-label">Projector</span></div>' +
'<div class="da-device da-vwall"><div class="da-vwall-grid"><div class="da-panel">' + DA_SCREEN + '</div><div class="da-panel">' + DA_SCREEN + '</div><div class="da-panel">' + DA_SCREEN + '</div><div class="da-panel">' + DA_SCREEN + '</div></div><span class="da-label">Video Wall</span></div>' +
'</div>';
var TS_MI = '<div class="ts-menu__item"><span class="ts-menu__name"></span><span class="ts-menu__dots"></span><span class="ts-menu__price"></span></div>';
var TS_COL = '<div class="ts-menu__col"><div class="ts-menu__cat"></div>' + TS_MI + TS_MI + TS_MI + '</div>';
var TS_HTML = '<div class="ts-stage" data-tv-stick-anim aria-hidden="true">' +
'<div class="ts-tv"><div class="ts-tv__body"><div class="ts-tv__screen"><div class="ts-slides">' +
'<div class="ts-slide ts-slide--menu"><div class="ts-menu"><div class="ts-menu__header"><div class="ts-menu__logo"></div><div class="ts-menu__title"></div></div><div class="ts-menu__cols">' + TS_COL + TS_COL + '</div></div></div>' +
'<div class="ts-slide ts-slide--wayfind"><div class="ts-wf"><div class="ts-wf__header"><div class="ts-wf__building"></div></div><div class="ts-wf__rows"><div class="ts-wf__row"><span class="ts-wf__arrow ts-wf__arrow--left"></span><span class="ts-wf__label ts-wf__label--w1"></span><span class="ts-wf__floor"></span></div><div class="ts-wf__row"><span class="ts-wf__arrow ts-wf__arrow--right"></span><span class="ts-wf__label ts-wf__label--w2"></span><span class="ts-wf__floor"></span></div><div class="ts-wf__row"><span class="ts-wf__arrow ts-wf__arrow--up"></span><span class="ts-wf__label ts-wf__label--w3"></span><span class="ts-wf__floor"></span></div><div class="ts-wf__row"><span class="ts-wf__arrow ts-wf__arrow--left"></span><span class="ts-wf__label ts-wf__label--w4"></span><span class="ts-wf__floor"></span></div></div></div></div>' +
'<div class="ts-slide ts-slide--sched"><div class="ts-sched"><div class="ts-sched__header"><div class="ts-sched__title"></div><div class="ts-sched__date"></div></div><div class="ts-sched__table"><div class="ts-sched__hrow"><span class="ts-sched__hcell"></span><span class="ts-sched__hcell"></span><span class="ts-sched__hcell"></span><span class="ts-sched__hcell"></span></div><div class="ts-sched__row"><span class="ts-sched__time"></span><span class="ts-sched__event ts-sched__event--a"></span><span class="ts-sched__cell"></span><span class="ts-sched__cell"></span></div><div class="ts-sched__row"><span class="ts-sched__time"></span><span class="ts-sched__cell"></span><span class="ts-sched__event ts-sched__event--b"></span><span class="ts-sched__cell"></span></div><div class="ts-sched__row"><span class="ts-sched__time"></span><span class="ts-sched__cell"></span><span class="ts-sched__cell"></span><span class="ts-sched__event ts-sched__event--c"></span></div><div class="ts-sched__row"><span class="ts-sched__time"></span><span class="ts-sched__event ts-sched__event--a"></span><span class="ts-sched__event ts-sched__event--b"></span><span class="ts-sched__cell"></span></div></div></div></div>' +
'</div></div><div class="ts-tv__port"></div></div><div class="ts-tv__feet"><div class="ts-tv__foot"></div><div class="ts-tv__foot"></div></div></div>' +
'<div class="ts-stick"><div class="ts-stick__body"><div class="ts-stick__led"></div></div><div class="ts-stick__connector"></div></div>' +
'</div>';
var NGD_ROW = '<div class="ngd-menu__row"><span class="ngd-menu__name"></span><span class="ngd-menu__price"></span></div>';
var NGD_ROWH = '<div class="ngd-menu__row ngd-menu__row--hl"><span class="ngd-menu__name"></span><span class="ngd-menu__price"></span></div>';
var NGD_HTML = '<div class="ngd-stage" aria-hidden="true">' +
'<div class="ngd-tv"><div class="ngd-tv__body"><div class="ngd-tv__screen"><div class="ngd-menu"><div class="ngd-menu__hd"><div class="ngd-menu__logo"></div><div class="ngd-menu__ttl"></div></div><div class="ngd-menu__cols"><div class="ngd-menu__col"><div class="ngd-menu__cat"></div>' + NGD_ROW + NGD_ROWH + NGD_ROW + '</div><div class="ngd-menu__col"><div class="ngd-menu__cat"></div>' + NGD_ROW + NGD_ROW + NGD_ROWH + '</div></div><div class="ngd-menu__ticker"><div class="ngd-menu__ticker-inner"></div></div></div></div><div class="ngd-tv__port"></div></div><div class="ngd-tv__feet"><div class="ngd-tv__foot"></div><div class="ngd-tv__foot"></div></div></div>' +
'<div class="ngd-player"><div class="ngd-player__connector"></div><div class="ngd-player__body"><div class="ngd-player__led"></div></div><div class="ngd-player__check"><svg viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="11" cy="11" r="10" stroke="#4CAF50" stroke-width="1.5"/><polyline points="6,11 9.5,14.5 16,7.5" stroke="#4CAF50" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></div></div>' +
'<div class="ngd-signal-wrap"><div class="ngd-cloud"><svg class="ngd-cloud__svg" viewBox="0 0 64 46" fill="none" xmlns="http://www.w3.org/2000/svg"><circle class="ngd-cloud__path" cx="32" cy="23" r="14" stroke-width="2.2" fill="none"/><path class="ngd-cloud__path" d="M18.5 23C23 20 41 20 45.5 23" stroke-width="1.9" stroke-linecap="round" fill="none"/><path class="ngd-cloud__path" d="M20.5 29C25.5 31 38.5 31 43.5 29" stroke-width="1.9" stroke-linecap="round" fill="none"/><path class="ngd-cloud__path" d="M24.5 12C21.5 16.5 21.5 29.5 24.5 34" stroke-width="1.9" stroke-linecap="round" fill="none"/><path class="ngd-cloud__path" d="M39.5 12C42.5 16.5 42.5 29.5 39.5 34" stroke-width="1.9" stroke-linecap="round" fill="none"/></svg></div><div class="ngd-signal-line"><div class="ngd-signal__dots"><div class="ngd-signal__dot ngd-signal__dot--1"></div><div class="ngd-signal__dot ngd-signal__dot--2"></div><div class="ngd-signal__dot ngd-signal__dot--3"></div></div><div class="ngd-signal__break"><svg viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="9" cy="9" r="8" fill="rgba(239,68,68,0.12)" stroke="#ef4444" stroke-width="1.2"/><line x1="5.5" y1="5.5" x2="12.5" y2="12.5" stroke="#ef4444" stroke-width="2" stroke-linecap="round"/><line x1="12.5" y1="5.5" x2="5.5" y2="12.5" stroke="#ef4444" stroke-width="2" stroke-linecap="round"/></svg></div></div></div>' +
'</div>';
var BD_SPLASH = '<div class="bd-splash"><div class="bd-splash__logo"></div></div>';
var BD_HDR = '<div class="bd-ui__header"><div class="bd-ui__logo"></div><div class="bd-ui__brand-bar"></div></div>';
var BD_HTML = '<div class="bd-stage" aria-hidden="true">' +
'<div class="bd-device bd-device--tablet"><span class="bd-device__label">Kiosk</span><div class="bd-device__body"><div class="bd-device__screen">' + BD_SPLASH + '<div class="bd-ui">' + BD_HDR + '<div class="bd-ui__content"><div class="bd-promo bd-promo--welcome"><div class="bd-promo__hero"></div><div class="bd-promo__heading"></div><div class="bd-promo__text"></div><div class="bd-promo__btn"></div></div></div></div></div></div></div>' +
'<div class="bd-device bd-device--wall"><span class="bd-device__label">Wall Display</span><div class="bd-device__body"><div class="bd-device__screen">' + BD_SPLASH + '<div class="bd-ui">' + BD_HDR + '<div class="bd-ui__content"><div class="bd-promo bd-promo--sale"><div class="bd-promo__cols"><div class="bd-promo__visual"></div><div class="bd-promo__info"><div class="bd-promo__badge"></div><div class="bd-promo__heading"></div><div class="bd-promo__text"></div><div class="bd-promo__text bd-promo__text--short"></div><div class="bd-promo__price"></div></div></div></div></div></div></div></div><div class="bd-mount"></div></div>' +
'<div class="bd-device bd-device--interactive"><span class="bd-device__label">Interactive</span><div class="bd-device__body"><div class="bd-device__screen">' + BD_SPLASH + '<div class="bd-ui">' + BD_HDR + '<div class="bd-ui__content"><div class="bd-promo bd-promo--menu"><div class="bd-promo__heading"></div><div class="bd-promo__grid"><div class="bd-promo__tile"><div class="bd-promo__tile-img"></div><div class="bd-promo__tile-lbl"></div></div><div class="bd-promo__tile"><div class="bd-promo__tile-img"></div><div class="bd-promo__tile-lbl"></div></div><div class="bd-promo__tile"><div class="bd-promo__tile-img"></div><div class="bd-promo__tile-lbl"></div></div><div class="bd-promo__tile"><div class="bd-promo__tile-img"></div><div class="bd-promo__tile-lbl"></div></div></div></div></div></div></div></div><div class="bd-table"></div></div>' +
'</div>';
var DB_HTML = '<div class="dashboard-tv" data-dashboard-container="true"><div class="dashboard-tv__body"><div class="dashboard-tv__screen"><svg viewBox="0 0 800 450" xmlns="http://www.w3.org/2000/svg" class="dashboard-chart" role="img" aria-label="Animated dashboard charts"><defs><linearGradient id="dbe-barGrad" x1="0%" y1="0%" x2="0%" y2="100%"><stop offset="0%" stop-color="#D83302" stop-opacity="1"/><stop offset="100%" stop-color="#4CAF50" stop-opacity=".8"/></linearGradient><linearGradient id="dbe-lineGrad" x1="0%" y1="0%" x2="0%" y2="100%"><stop offset="0%" stop-color="#4CAF50" stop-opacity=".3"/><stop offset="100%" stop-color="#4CAF50" stop-opacity="0"/></linearGradient></defs><g transform="translate(35,20)"><text class="ct" x="0" y="0" font-size="14" font-weight="600" fill="#888">Performance</text><g id="dbe-bars1" transform="translate(0,25)"><rect x="0" y="60" width="28" height="60" fill="url(#dbe-barGrad)"/><rect x="40" y="80" width="28" height="40" fill="url(#dbe-barGrad)"/><rect x="80" y="40" width="28" height="80" fill="url(#dbe-barGrad)"/><rect x="120" y="70" width="28" height="50" fill="url(#dbe-barGrad)"/><rect x="160" y="50" width="28" height="70" fill="url(#dbe-barGrad)"/></g><g transform="translate(0,152)"><text class="cl" x="14" y="0" font-size="10" text-anchor="middle" fill="#aaa">API</text><text class="cl" x="54" y="0" font-size="10" text-anchor="middle" fill="#aaa">Cache</text><text class="cl" x="94" y="0" font-size="10" text-anchor="middle" fill="#aaa">DB</text><text class="cl" x="134" y="0" font-size="10" text-anchor="middle" fill="#aaa">Queue</text><text class="cl" x="174" y="0" font-size="10" text-anchor="middle" fill="#aaa">Worker</text></g></g><g transform="translate(430,20)"><text class="ct" x="0" y="0" font-size="14" font-weight="600" fill="#888">Requests/sec</text><g transform="translate(0,25)"><rect x="0" y="50" width="28" height="70" fill="url(#dbe-barGrad)"/><rect x="40" y="30" width="28" height="90" fill="url(#dbe-barGrad)"/><rect x="80" y="65" width="28" height="55" fill="url(#dbe-barGrad)"/><rect x="120" y="45" width="28" height="75" fill="url(#dbe-barGrad)"/></g><g transform="translate(0,152)"><text class="cl" x="14" y="0" font-size="10" text-anchor="middle" fill="#aaa">Read</text><text class="cl" x="54" y="0" font-size="10" text-anchor="middle" fill="#aaa">Write</text><text class="cl" x="94" y="0" font-size="10" text-anchor="middle" fill="#aaa">Update</text><text class="cl" x="134" y="0" font-size="10" text-anchor="middle" fill="#aaa">Delete</text></g></g><g transform="translate(35,245)"><text class="ct" x="0" y="0" font-size="14" font-weight="600" fill="#888">Traffic Trend</text><g transform="translate(0,25)"><line class="grid-line" x1="0" y1="0" x2="340" y2="0" stroke="#ddd" stroke-width=".5"/><line class="grid-line" x1="0" y1="40" x2="340" y2="40" stroke="#ddd" stroke-width=".5"/><line class="grid-line" x1="0" y1="80" x2="340" y2="80" stroke="#ddd" stroke-width=".5"/><line class="grid-line" x1="0" y1="120" x2="340" y2="120" stroke="#ddd" stroke-width=".5"/><path d="M0,90 L42,70 L85,50 L127,80 L170,45 L212,65 L255,55 L297,75 L340,60 L340,145 L0,145 Z" fill="url(#dbe-lineGrad)"/><path d="M0,90 L42,70 L85,50 L127,80 L170,45 L212,65 L255,55 L297,75 L340,60" stroke="#4CAF50" stroke-width="2.5" fill="none" stroke-linecap="round"/></g></g><g transform="translate(490,245)"><text class="ct" x="100" y="0" font-size="14" font-weight="600" text-anchor="middle" fill="#888">Distribution</text><g transform="translate(100,90)"><path d="M0,0 L0,-55 A55,55 0 0,1 38.89,-38.89 Z" fill="#D83302" opacity=".9"/><path d="M0,0 L38.89,-38.89 A55,55 0 0,1 55,0 Z" fill="#4CAF50" opacity=".8"/><path d="M0,0 L55,0 A55,55 0 0,1 0,55 Z" fill="#f59e0b" opacity=".7"/><path d="M0,0 L0,55 A55,55 0 1,1 0,-55 Z" fill="#ef4444" opacity=".7"/><circle cx="0" cy="0" r="22" fill="#f5f5f5" stroke="#ddd" stroke-width="1"/></g></g></svg></div></div><div class="dashboard-tv__feet"><div class="dashboard-tv__foot"></div><div class="dashboard-tv__foot"></div></div></div>';
return el(Frag, null,
el(IC, null,
el(PB, { title: 'Row Settings' },
el(TC, { label: 'Visual (emoji)', value: a.visual, onChange: function(v){s({visual:v});} }),
el(TC, { label: 'Button URL', value: a.btnUrl, onChange: function(v){s({btnUrl:v});} }),
el(TG, { label: 'Reversed', checked: !!a.reversed, onChange: function(v){s({reversed:v});} }),
el(TG, { label: 'Dashboard Animation', checked: !!a.isDashboard, onChange: function(v){s({isDashboard:v});} }),
el(TG, { label: 'Device Animation', checked: !!a.deviceAnim, onChange: function(v){s({deviceAnim:v});} }),
el(TG, { label: 'TV Stick Animation', checked: !!a.tvStick, onChange: function(v){s({tvStick:v});} }),
el(TG, { label: 'Camera Animation', checked: !!a.cameraAnim, onChange: function(v){s({cameraAnim:v});} }),
el(TG, { label: 'Never Goes Dark', checked: !!a.neverGoesDark, onChange: function(v){s({neverGoesDark:v});} }),
el(TG, { label: 'Branded Display', checked: !!a.brandedAnim, onChange: function(v){s({brandedAnim:v});} })
),
el(PB, { title: 'Gallery TV Slideshow', initialOpen: false },
el(MUC, null,
el(MU, {
onSelect: function(media) {
s({ galleryIds: media.map(function(m){ return m.id; }) });
},
allowedTypes: ['image'],
gallery: true,
multiple: true,
value: a.galleryIds || [],
render: function(ref) {
return el(Frag, null,
a.galleryIds && a.galleryIds.length
? el('div', { style: { marginBottom: '8px' } },
el('p', { style: { margin: '0 0 4px' } }, a.galleryIds.length + ' image(s) selected'),
el(Btn, { variant: 'link', isDestructive: true, onClick: function(){ s({ galleryIds: [] }); } }, 'Clear gallery')
)
: null,
el(Btn, { onClick: ref.open, variant: 'secondary', __next40pxDefaultSize: true },
a.galleryIds && a.galleryIds.length ? 'Edit gallery' : 'Select images for TV slideshow')
);
}
})
)
),
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: 600, step: 4,
onChange: function(v){ s({ imgWidth: v }); } }) : null
)
),
el('div', { className: 'platform-row' + (a.reversed ? ' reverse' : '') },
el('div', { className: 'platform-text' },
el(RT, { tagName: 'h3', value: a.heading, onChange: function(v){s({heading:v});}, placeholder: 'Service name...' }),
el(RT, { tagName: 'p', value: a.description, onChange: function(v){s({description:v});}, placeholder: 'Service description...' }),
a.btnUrl ? el(RT, { tagName: 'span', className: 'btn btn-outline mt-3',
value: a.btnText, onChange: function(v){s({btnText:v});}, placeholder: 'Button...' }) : null
),
a.isDashboard
? el('div', { className: 'platform-visual has-dashboard', dangerouslySetInnerHTML: { __html: DB_HTML } })
: a.cameraAnim
? el('div', { className: 'platform-visual has-camera' },
el('div', { className: 'cam-stage', 'aria-hidden': 'true' },
// Photo camera (left)
el('div', { className: 'pc-wrap' },
el('div', { className: 'pc-body' },
el('div', { className: 'pc-flash-unit' }),
el('div', { className: 'pc-top' },
el('div', { className: 'pc-shutter-btn' }),
el('div', { className: 'pc-viewfinder' })
),
el('div', { className: 'pc-front' },
el('div', { className: 'pc-lens-ring' },
el('div', { className: 'pc-lens-glass' },
el('div', { className: 'pc-lens-reflex' })
)
),
el('div', { className: 'pc-grip' })
)
),
el('div', { className: 'pc-prints' },
el('div', { className: 'pc-print pc-print--1', style: { opacity: '1', transform: 'rotate(-12deg) translateY(0)' } },
el('div', { className: 'pc-print__img' })
)
)
),
// Centre scene
el('div', { className: 'cam-scene' },
el('div', { className: 'cam-subject cam-subject--1' }),
el('div', { className: 'cam-flash-overlay' }),
el('div', { className: 'cam-vid-overlay' })
),
// Video camera on tripod (right)
el('div', { className: 'vc-wrap' },
el('div', { className: 'vc-camera' },
el('div', { className: 'vc-handle' }),
el('div', { className: 'vc-body' },
el('div', { className: 'vc-lens-barrel' },
el('div', { className: 'vc-lens-tip' },
el('div', { className: 'vc-lens-glass' },
el('div', { className: 'vc-lens-reflex' })
)
)
),
el('div', { className: 'vc-top-rail' }),
el('div', { className: 'vc-rec-light' }),
el('div', { className: 'vc-eyepiece' })
)
),
el('div', { className: 'vc-tripod' },
el('div', { className: 'vc-stem' }),
el('div', { className: 'vc-legs' },
el('div', { className: 'vc-leg vc-leg--l' }),
el('div', { className: 'vc-leg vc-leg--c' }),
el('div', { className: 'vc-leg vc-leg--r' })
)
)
)
)
)
: a.deviceAnim
? el('div', { className: 'platform-visual has-anim', dangerouslySetInnerHTML: { __html: DA_HTML } })
: a.tvStick
? el('div', { className: 'platform-visual has-tv-stick', dangerouslySetInnerHTML: { __html: TS_HTML } })
: a.neverGoesDark
? el('div', { className: 'platform-visual has-ngd', dangerouslySetInnerHTML: { __html: NGD_HTML } })
: a.brandedAnim
? el('div', { className: 'platform-visual has-branded', dangerouslySetInnerHTML: { __html: BD_HTML } })
: a.imgUrl
? el('div', { className: 'platform-visual has-img' },
el('img', { src: a.imgUrl, style: { width: imgW + 'px', maxWidth: '100%', height: 'auto', borderRadius: '4px', objectFit: 'contain', display: 'block', marginInline: 'auto' } })
)
: el('div', { className: 'platform-visual' }, a.visual || '\uD83D\uDCBB')
)
);
},
save: function () { return null; }
});
/* ── Trust Item ───────────────────────────────────────────────────────── */
reg('oribi/trust-item', {
title: 'Trust Item',
icon: 'shield',
category: 'oribi',
parent: ['oribi/trust-section'],
supports: { html: false, reusable: false },
attributes: {
heading: { type: 'string', default: '' },
description: { type: 'string', default: '' },
},
edit: function (props) {
var a = props.attributes, s = props.setAttributes;
return el('div', { className: 'trust-item' },
el(RT, { tagName: 'h3', style: { marginBottom: '1rem' },
value: a.heading, onChange: function(v){s({heading:v});}, placeholder: 'Sub-heading...' }),
el(RT, { tagName: 'p', value: a.description,
onChange: function(v){s({description:v});}, placeholder: 'Description...' })
);
},
save: function () { return null; }
});
/* ═══════════════════════════════════════════════════════════════════════
PARENT BLOCKS (use InnerBlocks for child items)
═══════════════════════════════════════════════════════════════════════ */
/* 3. FEATURE SECTION ──────────────────────────────────────────────────── */
reg('oribi/feature-section', {
title: 'Oribi Feature Section',
icon: 'grid-view',
category: 'oribi',
supports: { align: ['full'], html: false },
attributes: SECTION_ATTRS,
edit: createCardSectionEdit(['oribi/feature-card'], [['oribi/feature-card', {}]], 'Feature Card'),
save: function () { return el(IB.Content); }
});
/* VALUE SECTION ────────────────────────────────────────────────────────── */
reg('oribi/value-section', {
title: 'Oribi Value Section',
icon: 'heart',
category: 'oribi',
supports: { align: ['full'], html: false },
attributes: SECTION_ATTRS,
edit: createCardSectionEdit(['oribi/value-card'], [['oribi/value-card', {}]], 'Value Card'),
save: function () { return el(IB.Content); }
});
/* ADDON SECTION ────────────────────────────────────────────────────────── */
reg('oribi/addon-section', {
title: 'Oribi Addon Section',
icon: 'plus-alt2',
category: 'oribi',
supports: { align: ['full'], html: false },
attributes: SECTION_ATTRS,
edit: createCardSectionEdit(['oribi/addon-card'], [['oribi/addon-card', {}]], 'Addon Card'),
save: function () { return el(IB.Content); }
});
/* IMAGE SECTION ────────────────────────────────────────────────────────── */
reg('oribi/image-section', {
title: 'Oribi Image Section',
icon: 'format-image',
category: 'oribi',
supports: { align: ['full'], html: false },
attributes: SECTION_ATTRS,
edit: createCardSectionEdit(['oribi/image-card'], [['oribi/image-card', {}]], 'Image Card'),
save: function () { return el(IB.Content); }
});
/* STAT SECTION ─────────────────────────────────────────────────────────── */
reg('oribi/stat-section', {
title: 'Oribi Stat Section',
icon: 'chart-bar',
category: 'oribi',
supports: { align: ['full'], html: false },
attributes: SECTION_ATTRS,
edit: createCardSectionEdit(['oribi/stat-card'], [['oribi/stat-card', {}]], 'Stat Card'),
save: function () { return el(IB.Content); }
});
/* LINK SECTION ─────────────────────────────────────────────────────────── */
reg('oribi/link-section', {
title: 'Oribi Link Section',
icon: 'admin-links',
category: 'oribi',
supports: { align: ['full'], html: false },
attributes: SECTION_ATTRS,
edit: createCardSectionEdit(['oribi/link-card'], [['oribi/link-card', {}]], 'Link Card'),
save: function () { return el(IB.Content); }
});
/* 4. PRICING SECTION ──────────────────────────────────────────────────── */
reg('oribi/pricing-section', {
title: 'Oribi Pricing',
icon: 'money-alt',
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: '' },
lead: { type: 'string', default: '' },
},
edit: 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(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: 'Pricing heading...' }),
el(RT, { tagName: 'p', className: 'lead', value: a.lead, onChange: function(v){s({lead:v});}, placeholder: 'Lead text...' })
),
el('div', { className: 'pricing-grid' },
el(IB, {
allowedBlocks: ['oribi/pricing-card'],
template: [
['oribi/pricing-card', { name: 'Essentials' }],
['oribi/pricing-card', { name: 'Pro', featured: true, badge: 'Most Popular' }],
['oribi/pricing-card', { name: 'Enterprise' }]
],
templateLock: false
})
)
)
)
);
},
save: function () { return el(IB.Content); }
});
/* 7. PLATFORM SECTION ─────────────────────────────────────────────────── */
reg('oribi/platform-section', {
title: 'Oribi Platform Section',
icon: 'slides',
category: 'oribi',
supports: { align: ['full'], html: false },
attributes: {
align: { type: 'string', default: 'full' },
label: { type: 'string', default: '' },
heading: { type: 'string', default: '' },
lead: { type: 'string', default: '' },
},
edit: function (props) {
var a = props.attributes, s = props.setAttributes;
return el(Frag, null,
el(IC, null,
el(PB, { title: 'Section' },
el(TC, { label: 'Label', value: a.label, onChange: function(v){s({label:v});} })
)
),
el('section', { className: '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(IB, {
allowedBlocks: ['oribi/platform-row'],
template: [['oribi/platform-row', {}]],
templateLock: false
})
)
)
);
},
save: function () { return el(IB.Content); }
});
/* 9. TRUST SECTION ────────────────────────────────────────────────────── */
reg('oribi/trust-section', {
title: 'Oribi Trust Section',
icon: 'shield',
category: 'oribi',
supports: { align: ['full'], html: false },
attributes: {
align: { type: 'string', default: 'full' },
label: { type: 'string', default: '' },
heading: { type: 'string', default: '' },
lead: { type: 'string', default: '' },
btnText: { type: 'string', default: '' },
btnUrl: { type: 'string', default: '' },
btnSub: { 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', value: a.label, onChange: function(v){s({label:v});} }),
el(TC, { label: 'Button Text', value: a.btnText, onChange: function(v){s({btnText:v});} }),
el(TC, { label: 'Button URL', value: a.btnUrl, onChange: function(v){s({btnUrl:v});} }),
el(TC, { label: 'Button Subtext', value: a.btnSub, onChange: function(v){s({btnSub:v});} })
)
),
el('section', { className: '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: 'Heading...' }),
el(RT, { tagName: 'p', className: 'lead', value: a.lead, onChange: function(v){s({lead:v});}, placeholder: 'Lead text...' })
),
el('div', { className: 'grid-2', style: { alignItems: 'center' } },
el('div', { style: { display: 'flex', flexDirection: 'column', gap: '1.5rem' } },
el(IB, {
allowedBlocks: ['oribi/trust-item'],
template: [['oribi/trust-item', {}]],
templateLock: false
})
),
el('div', { style: { textAlign: 'center' } },
el('span', { className: 'btn btn-primary btn-lg' }, a.btnText || 'Button'),
a.btnSub ? el('p', { className: 'lead mt-2', style: { fontSize: '.9rem' } }, a.btnSub) : null
)
)
)
)
);
},
save: function () { return el(IB.Content); }
});
/* 10. FAQ SECTION ─────────────────────────────────────────────────────── */
reg('oribi/faq-section', {
title: 'Oribi FAQ Section',
icon: 'editor-help',
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: '' },
lead: { type: 'string', default: '' },
},
edit: 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(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: 'FAQ heading...' }),
el(RT, { tagName: 'p', className: 'lead', value: a.lead, onChange: function(v){s({lead:v});}, placeholder: 'Lead text...' })
),
el('div', { className: 'faq-list' },
el(IB, {
allowedBlocks: ['oribi/faq-item'],
template: [['oribi/faq-item', {}]],
templateLock: false
})
)
)
)
);
},
save: function () { return el(IB.Content); }
});
/* FAQ ITEM (child) ─────────────────────────────────────────────────────── */
reg('oribi/faq-item', {
title: 'Oribi FAQ Item',
icon: 'editor-help',
category: 'oribi',
parent: ['oribi/faq-section'],
supports: { html: false, reusable: false },
attributes: {
question: { type: 'string', default: '' },
answer: { type: 'string', default: '' },
},
edit: function (props) {
var a = props.attributes, s = props.setAttributes;
return el('details', { className: 'faq-item', open: true },
el('summary', { className: 'faq-question' },
el(RT, { tagName: 'span', value: a.question,
onChange: function(v){s({question:v});}, placeholder: 'Question...' })
),
el('div', { className: 'faq-answer' },
el(RT, { tagName: 'p', value: a.answer,
onChange: function(v){s({answer:v});}, placeholder: 'Answer...' })
)
);
},
save: function () { return null; }
});
/* 11. COMPARISON TABLE (standalone) ───────────────────────────────────── */
reg('oribi/comparison-table', {
title: 'Oribi Comparison Table',
icon: 'editor-table',
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: '' },
lead: { type: 'string', default: '' },
columns: { type: 'array', default: [] },
rows: { type: 'array', default: [] },
},
edit: function (props) {
var a = props.attributes, s = props.setAttributes;
var cols = a.columns || [];
var rows = a.rows || [];
/* ── Column helpers ─────────────────────────────────────── */
function updateCol(idx, val) {
var c = cols.slice(); c[idx] = val; s({ columns: c });
}
function addCol() {
var newRows = rows.map(function(r) {
if (r.group) return r;
return Object.assign({}, r, { values: (r.values || []).concat([false]) });
});
s({ columns: cols.concat(['Plan']), rows: newRows });
}
function removeCol(idx) {
var c = cols.slice(); c.splice(idx, 1);
var newRows = rows.map(function(r) {
if (r.group) return r;
var v = (r.values || []).slice(); v.splice(idx, 1);
return Object.assign({}, r, { values: v });
});
s({ columns: c, rows: newRows });
}
/* ── Row helpers ────────────────────────────────────────── */
function updateRow(idx, key, val) {
var r = rows.slice();
r[idx] = Object.assign({}, r[idx]);
r[idx][key] = val;
s({ rows: r });
}
function updateCell(ri, ci, val) {
var r = rows.slice();
r[ri] = Object.assign({}, r[ri]);
var v = (r[ri].values || []).slice();
v[ci] = val;
r[ri].values = v;
s({ rows: r });
}
function toggleCell(ri, ci) {
var val = (rows[ri].values || [])[ci];
updateCell(ri, ci, val === true ? false : val === false ? true : true);
}
function switchCellToText(ri, ci) { updateCell(ri, ci, ''); }
function addFeatureRow() {
var vals = cols.map(function() { return false; });
s({ rows: rows.concat([{ feature: 'New feature', values: vals }]) });
}
function addGroupRow() {
s({ rows: rows.concat([{ group: 'New Group' }]) });
}
function removeRow(idx) {
var r = rows.slice(); r.splice(idx, 1); s({ rows: r });
}
function moveRow(idx, dir) {
var t = idx + dir;
if (t < 0 || t >= rows.length) return;
var r = rows.slice();
var tmp = r[idx]; r[idx] = r[t]; r[t] = tmp;
s({ rows: r });
}
/* ── Inline styles for editor controls ──────────────────── */
var inputStyle = { width: '100%', padding: '4px 6px', border: '1px solid #ddd', borderRadius: '3px', fontSize: '13px', background: 'transparent', boxSizing: 'border-box' };
var thInputStyle = Object.assign({}, inputStyle, { fontWeight: 600, textAlign: 'center' });
var smallBtnStyle = { background: 'none', border: 'none', cursor: 'pointer', padding: '2px 4px', fontSize: '11px', lineHeight: 1, opacity: 0.6, verticalAlign: 'middle' };
var rowCtrlStyle = { whiteSpace: 'nowrap', width: '1%', padding: '4px', verticalAlign: 'middle', border: 'none', background: 'transparent' };
var cellBtnStyle = { background: 'none', border: '1px solid #ddd', borderRadius: '3px', cursor: 'pointer', padding: '2px 6px', fontSize: '13px', lineHeight: '1.4', margin: '0 1px' };
/* ── Render ─────────────────────────────────────────────── */
return el(Frag, null,
el(IC, null,
el(PB, { title: 'Table 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(PB, { title: 'Columns', initialOpen: false },
cols.map(function(col, i) {
return el('div', { key: i, style: { display: 'flex', gap: '4px', marginBottom: '8px', alignItems: 'center' } },
el(TC, { label: '', value: col, onChange: function(v){ updateCol(i, v); }, style: { flex: 1 } }),
el(Btn, { isDestructive: true, isSmall: true, onClick: function(){ removeCol(i); } }, '\u2715')
);
}),
el(Btn, { isSecondary: true, isSmall: true, onClick: addCol }, '+ Add Column')
),
el(PB, { title: 'Add Rows', initialOpen: false },
el('div', { style: { display: 'flex', gap: '8px' } },
el(Btn, { isSecondary: true, isSmall: true, onClick: addFeatureRow }, '+ Feature Row'),
el(Btn, { isSecondary: true, isSmall: true, onClick: addGroupRow }, '+ Group Row')
)
)
),
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: 'Table heading...' }),
el(RT, { tagName: 'p', className: 'lead', value: a.lead, onChange: function(v){s({lead:v});}, placeholder: 'Lead text...' })
),
el('div', { className: 'comparison-table-wrap' },
el('table', { className: 'comparison-table' },
el('thead', null,
el('tr', null,
el('th', { className: 'comparison-feature-col' }, 'Feature'),
cols.map(function(col, i) {
return el('th', { key: i },
el('input', { type: 'text', value: col, style: thInputStyle, onChange: function(e){ updateCol(i, e.target.value); } })
);
}),
el('th', { style: rowCtrlStyle })
)
),
el('tbody', null,
rows.map(function(row, ri) {
if (row.group) {
return el('tr', { key: ri, className: 'comparison-group-row' },
el('td', { colSpan: cols.length + 1 },
el('input', { type: 'text', value: row.group, style: Object.assign({}, inputStyle, { fontWeight: 700 }), onChange: function(e){ updateRow(ri, 'group', e.target.value); } })
),
el('td', { style: rowCtrlStyle },
el('button', { style: smallBtnStyle, onClick: function(){ moveRow(ri, -1); }, title: 'Move up' }, '\u25B2'),
el('button', { style: smallBtnStyle, onClick: function(){ moveRow(ri, 1); }, title: 'Move down' }, '\u25BC'),
el('button', { style: Object.assign({}, smallBtnStyle, { color: '#b00' }), onClick: function(){ removeRow(ri); }, title: 'Remove row' }, '\u2715')
)
);
}
return el('tr', { key: ri },
el('td', { className: 'comparison-feature-name' },
el('input', { type: 'text', value: row.feature || '', style: inputStyle, placeholder: 'Feature name\u2026', onChange: function(e){ updateRow(ri, 'feature', e.target.value); } })
),
(row.values || []).map(function(val, ci) {
if (typeof val === 'boolean') {
return el('td', { key: ci, className: 'comparison-cell', style: { textAlign: 'center' } },
el('button', { style: Object.assign({}, cellBtnStyle, { color: val ? '#2e7d32' : '#c62828' }), onClick: function(){ toggleCell(ri, ci); }, title: 'Toggle \u2713/\u2717' },
val ? '\u2713' : '\u2717'
),
el('button', { style: Object.assign({}, smallBtnStyle, { fontSize: '10px' }), onClick: function(){ switchCellToText(ri, ci); }, title: 'Switch to text' }, 'Aa')
);
}
return el('td', { key: ci, className: 'comparison-cell' },
el('div', { style: { display: 'flex', alignItems: 'center', gap: '2px' } },
el('input', { type: 'text', value: String(val), style: Object.assign({}, inputStyle, { flex: 1, minWidth: '60px' }), placeholder: 'Value\u2026', onChange: function(e){ updateCell(ri, ci, e.target.value); } }),
el('button', { style: Object.assign({}, smallBtnStyle, { color: '#2e7d32' }), onClick: function(){ updateCell(ri, ci, true); }, title: 'Set as \u2713' }, '\u2713'),
el('button', { style: Object.assign({}, smallBtnStyle, { color: '#c62828' }), onClick: function(){ updateCell(ri, ci, false); }, title: 'Set as \u2717' }, '\u2717')
)
);
}),
el('td', { style: rowCtrlStyle },
el('button', { style: smallBtnStyle, onClick: function(){ moveRow(ri, -1); }, title: 'Move up' }, '\u25B2'),
el('button', { style: smallBtnStyle, onClick: function(){ moveRow(ri, 1); }, title: 'Move down' }, '\u25BC'),
el('button', { style: Object.assign({}, smallBtnStyle, { color: '#b00' }), onClick: function(){ removeRow(ri); }, title: 'Remove row' }, '\u2715')
)
);
})
)
),
el('div', { style: { display: 'flex', gap: '8px', marginTop: '12px', justifyContent: 'center' } },
el(Btn, { isSecondary: true, isSmall: true, onClick: addFeatureRow }, '+ Feature Row'),
el(Btn, { isSecondary: true, isSmall: true, onClick: addGroupRow }, '+ Group Row'),
el(Btn, { isSecondary: true, isSmall: true, onClick: addCol }, '+ Column')
)
)
)
)
);
},
save: function () { return null; }
});
/* ═══════════════════════════════════════════════════════════════════════
TEMPLATE-PART HELPER BLOCKS
═══════════════════════════════════════════════════════════════════════ */
/* ═══════════════════════════════════════════════════════════════════════
ANIMATED HERO BLOCKS (OTS Signs)
═══════════════════════════════════════════════════════════════════════ */
/* ANIMATED HERO ───────────────────────────────────────────────────────── */
reg('oribi/hero-animated', {
title: 'Animated Hero',
icon: 'admin-site-alt3',
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 Started' },
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: '' },
stat3Value: { type: 'string', default: '' },
stat3Label: { type: 'string', default: '' },
},
edit: function (props) {
var a = props.attributes, s = props.setAttributes;
// Build particle elements for editor preview
var particles = [];
for (var i = 1; i <= 12; i++) {
particles.push(el('div', { key: 'p' + i, className: 'hero-particle hero-particle--' + i }));
}
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 hero-animated' },
el('div', { className: 'hero-particles', 'aria-hidden': 'true' }, particles),
el('div', { className: 'hero-animated__glow' }),
el('div', { className: 'container hero-animated__inner' },
el('div', { className: 'hero-animated__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 hero-stats--three' },
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', null,
el(RT, { tagName: 'div', className: 'hero-stat-value', value: a.stat3Value,
onChange: function(v){s({stat3Value:v});}, placeholder: '\u2014', allowedFormats: [] }),
el(RT, { tagName: 'div', className: 'hero-stat-label', value: a.stat3Label,
onChange: function(v){s({stat3Label:v});}, placeholder: 'Stat label', allowedFormats: [] })
)
)
)
)
)
);
},
save: function () { return null; }
});
/* ANIMATED PAGE HERO ──────────────────────────────────────────────────── */
reg('oribi/page-hero-animated', {
title: 'Animated 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;
var particles = [];
for (var i = 1; i <= 8; i++) {
particles.push(el('div', { key: 'p' + i, className: 'hero-particle hero-particle--' + i }));
}
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 page-hero-animated' },
el('div', { className: 'hero-particles', 'aria-hidden': 'true' }, particles),
el('div', { className: 'hero-animated__glow' }),
el('div', { className: 'hero-overlay' }),
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; }
});
/* ═══════════════════════════════════════════════════════════════════════
TEMPLATE-PART HELPER BLOCKS
═══════════════════════════════════════════════════════════════════════ */
reg('oribi/site-header', {
title: 'Oribi Site Header',
icon: 'admin-home',
category: 'oribi',
supports: { html: false, multiple: false, reusable: false },
edit: function () {
return el('div', {
style: { background: '#111111', color: '#fff', padding: '20px 24px', borderRadius: '8px', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }
},
el('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } },
el('strong', { style: { fontSize: '1.2rem' } }, 'Oribi'),
el('span', { style: { fontSize: '1.2rem', fontWeight: 300 } }, 'Tech')
),
el('div', { style: { display: 'flex', gap: '1.5rem', fontSize: '.9rem', opacity: 0.7 } },
el('span', null, 'Services'),
el('span', null, 'About'),
el('span', null, 'FAQ'),
el('span', null, 'Contact')
)
);
},
save: function () { return null; }
});
reg('oribi/site-footer', {
title: 'Oribi Site Footer',
icon: 'admin-home',
category: 'oribi',
supports: { html: false, multiple: false, reusable: false },
edit: function () {
return el('div', {
style: { background: '#111111', color: '#fff', padding: '24px', borderRadius: '8px', textAlign: 'center' }
},
el('strong', { style: { fontSize: '1.1rem' } }, 'OTS Theme - Site Footer'),
el('p', { style: { opacity: 0.5, margin: '8px 0 0', fontSize: '.85rem' } }, 'Brand · Service Links · Company Links · Connect · Copyright')
);
},
save: function () { return null; }
});
})(window.wp);