Files
OTSSigns-Website/theme/blocks/editor.js

2013 lines
125 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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 || [];
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('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 }, col); })
)
),
el('tbody', null,
rows.map(function(row, i) {
if (row.group) {
return el('tr', { key: i, className: 'comparison-group-row' },
el('td', { colSpan: cols.length + 1 }, row.group)
);
}
return el('tr', { key: i },
el('td', { className: 'comparison-feature-name' }, row.feature || ''),
(row.values || []).map(function(val, j) {
return el('td', { key: j, className: 'comparison-cell' },
val === true ? '\u2713' : val === false ? '\u2014' : String(val)
);
})
);
})
)
)
)
)
)
);
},
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; }
});
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; }
});
/* USE CASES SHOWCASE ──────────────────────────────────────────────────── */
reg('oribi/use-cases', {
title: 'Use Cases Showcase',
icon: 'grid-view',
category: 'oribi',
supports: { html: false },
attributes: {
label: { type: 'string', default: '\u25cf Use Cases' },
heading: { type: 'string', default: 'Built for Every Scenario' },
lead: { type: 'string', default: 'From menus to meeting-room dashboards, digital signage adapts to your environment.' },
case1Title: { type: 'string', default: 'Menu Boards' },
case1Desc: { type: 'string', default: 'Showcase food, drinks, and daily specials with dynamic, always-current displays.' },
case2Title: { type: 'string', default: 'Event Displays' },
case2Desc: { type: 'string', default: 'Promote schedules, speakers, and live countdowns across lobbies and venues.' },
case3Title: { type: 'string', default: 'Office Dashboards' },
case3Desc: { type: 'string', default: 'Surface KPIs, occupancy data, and team alerts on screens throughout your workspace.' },
case4Title: { type: 'string', default: 'Wayfinding' },
case4Desc: { type: 'string', default: 'Guide visitors through complex buildings with interactive maps and directional signs.' },
},
edit: function (props) {
var a = props.attributes, s = props.setAttributes;
return el(Frag, null,
el(IC, null,
el(PB, { title: 'Section Header' },
el(TC, { label: 'Label', value: a.label, onChange: function(v){s({label:v});} }),
el(TC, { label: 'Heading', value: a.heading, onChange: function(v){s({heading:v});} }),
el(TA, { label: 'Lead', value: a.lead, onChange: function(v){s({lead:v});} })
),
el(PB, { title: 'Use Case 1 Menu Boards', initialOpen: false },
el(TC, { label: 'Title', value: a.case1Title, onChange: function(v){s({case1Title:v});} }),
el(TA, { label: 'Description', value: a.case1Desc, onChange: function(v){s({case1Desc:v});} })
),
el(PB, { title: 'Use Case 2 Events', initialOpen: false },
el(TC, { label: 'Title', value: a.case2Title, onChange: function(v){s({case2Title:v});} }),
el(TA, { label: 'Description', value: a.case2Desc, onChange: function(v){s({case2Desc:v});} })
),
el(PB, { title: 'Use Case 3 Dashboards', initialOpen: false },
el(TC, { label: 'Title', value: a.case3Title, onChange: function(v){s({case3Title:v});} }),
el(TA, { label: 'Description', value: a.case3Desc, onChange: function(v){s({case3Desc:v});} })
),
el(PB, { title: 'Use Case 4 Wayfinding', initialOpen: false },
el(TC, { label: 'Title', value: a.case4Title, onChange: function(v){s({case4Title:v});} }),
el(TA, { label: 'Description', value: a.case4Desc, onChange: function(v){s({case4Desc:v});} })
)
),
el('section', { className: 'section use-cases-section' },
el('div', { className: 'container' },
el('div', { className: 'section-header' },
a.label ? el('span', { className: 'section-label' }, a.label) : null,
a.heading ? el('h2', null, a.heading) : null,
a.lead ? el('p', { className: 'lead' }, a.lead) : null
),
el('div', { className: 'uc-track' },
['menu', 'event', 'dashboard', 'wayfinding'].map(function (mod, i) {
var n = i + 1;
var title = a['case' + n + 'Title'];
var desc = a['case' + n + 'Desc'];
return el('div', { key: mod, className: 'uc-item' },
el('div', { className: 'uc-circle uc-anim--' + mod,
style: { display:'flex', alignItems:'center', justifyContent:'center',
fontSize:'.7rem', color:'var(--color-muted)', textAlign:'center', padding:'8px' } },
mod
),
el('div', { className: 'uc-item-body' },
el('div', { className: 'uc-item-title' }, title),
desc ? el('p', { className: 'uc-item-desc' }, desc) : null
)
);
})
)
)
)
);
},
save: function () { return null; }
});
})(window.wp);