From 71d63b67f2537af6cdfd16c96f6ce9838d1049dd Mon Sep 17 00:00:00 2001 From: Matt Batchelder Date: Sun, 22 Feb 2026 11:54:55 -0500 Subject: [PATCH 01/38] feat: Enhance footer branding with company logo and name display --- theme/assets/css/main.css | 17 +++++++++++++++++ theme/blocks/index.php | 13 ++++++++----- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/theme/assets/css/main.css b/theme/assets/css/main.css index d87a104..4ca9091 100644 --- a/theme/assets/css/main.css +++ b/theme/assets/css/main.css @@ -3416,6 +3416,22 @@ p:last-child { margin-bottom: 0; } .footer-inner { grid-template-columns: 1fr; } } +.logo-with-company { + display: flex; + align-items: center; + gap: 1rem; + margin-bottom: 1rem; +} +.logo-with-company img { + height: auto; + max-width: 60px; +} +.company-name { + font-size: 1.2rem; + font-weight: 600; + color: #fff; + line-height: 1.2; +} .footer-brand .logo-text { font-size: 1.5rem; color: #fff; } .footer-tagline { font-size: .9rem; color: rgba(255,255,255,.5); margin-top: .75rem; margin-bottom: .5rem; } .footer-location { font-size: .8rem; color: rgba(255,255,255,.4); } @@ -3712,6 +3728,7 @@ p:last-child { margin-bottom: 0; } } [data-theme="dark"] .site-footer { background: #0D0D0D; } +[data-theme="dark"] .company-name { color: #fff; } [data-theme="dark"] .site-nav.open { background: #111111; } diff --git a/theme/blocks/index.php b/theme/blocks/index.php index cf382cb..3f610f1 100644 --- a/theme/blocks/index.php +++ b/theme/blocks/index.php @@ -723,11 +723,14 @@ function oribi_render_site_footer() {
@@ -868,9 +894,9 @@ function oribi_render_hero( $a ) {
@@ -887,8 +913,8 @@ function oribi_render_hero( $a ) { @@ -900,48 +926,53 @@ function oribi_render_hero( $a ) { } /* ── Page Hero ─────────────────────────────────────────────────────────────── */ -function oribi_render_page_hero( $a ) { +function oribi_render_page_hero($a) +{ ob_start(); ?>
- -

-

+ +

+

-

-

- +

+

+
-
+
> > - -

-

+ +

+

-
>
+
>
@@ -949,21 +980,22 @@ function oribi_render_intro_section( $a ) { } /* ── Contact Section ───────────────────────────────────────────────────────── */ -function oribi_render_contact_section( $a ) { +function oribi_render_contact_section($a) +{ ob_start(); ?>
-

-

-
Email Us
-
Existing Customer Support
-
Client Portal
-
Location
+

+

+
Email Us
+
Existing Customer Support
+
Client Portal
+
Location
-

+

@@ -994,289 +1026,347 @@ function oribi_render_contact_section( $a ) { } /* ── Feature Section (parent - wraps child feature-card blocks) ────────────── */ -function oribi_render_feature_section( $a, $content ) { - return oribi_render_card_section( $a, $content, 'grid', 3 ); +function oribi_render_feature_section($a, $content) +{ + return oribi_render_card_section($a, $content, 'grid', 3); } /* ── Feature Card (child - renders one card) ───────────────────────────────── */ -function oribi_render_feature_card( $a ) { - $tag = ! empty( $a['url'] ) ? 'a' : 'div'; - $href = ! empty( $a['url'] ) ? ' href="' . esc_url( $a['url'] ) . '"' : ''; - $link_cls = ! empty( $a['url'] ) ? ' feature-card-link' : ''; - $center = ! empty( $a['centered'] ); - $img = oribi_card_image_html( $a ); - $img_cls = $img['card_class'] ? ' ' . $img['card_class'] : ''; +function oribi_render_feature_card($a) +{ + $tag = !empty($a['url']) ? 'a' : 'div'; + $href = !empty($a['url']) ? ' href="' . esc_url($a['url']) . '"' : ''; + $link_cls = !empty($a['url']) ? ' feature-card-link' : ''; + $center = !empty($a['centered']); + $img = oribi_card_image_html($a); + $img_cls = $img['card_class'] ? ' ' . $img['card_class'] : ''; ob_start(); - if ( $img['html'] && $img['position'] === 'left' ) : ?> -< class="oribi-card"> + if ($img['html'] && $img['position'] === 'left'): ?> +< class="oribi-card">
-

-

+

+

> - -< class="oribi-card"> + +< class="oribi-card">
-
>
-

-

+
>
+

+

> - -< class="oribi-card"> - + +< class="oribi-card"> + - - -
>
- -

-

+ + +
>
+ +

+

> - -
+ if ($img['html'] && $img['position'] === 'background'): ?> +
-
-

-

+
+

+

- -
+ +
-

-

+

+

- -
- - -
- -

-

+ +
+ + +
+ +

+

- -
+ if ($img['html'] && $img['position'] === 'left'): ?> +
- -

-

+ +

+

- -
+ +
-
- -

-

+
+ +

+

- -
- - -
- - -

-

+ +
+ + +
+ + +

+

- -< class="oribi-card image-card"> + if ($img['html'] && $img['position'] === 'left'): ?> +< class="oribi-card image-card">
-

-

+

+

> - -< class="oribi-card image-card"> + +< class="oribi-card image-card">
-
-

-

+
+

+

> - -< class="oribi-card image-card"> - - -
- + +< class="oribi-card image-card"> + + +
+
-

-

+

+

> - -
+ if ($img['html'] && $img['position'] === 'background'): ?> +
-
-
-
-

+
+
+
+

- -
+ +
-
-
-

+
+
+

- -
- -
-
-
-

+ +
+ +
+
+
+

-' - . esc_html( $a['linkText'] ?? 'Learn More' ) . ''; + if (!empty($a['linkUrl'])) { + $cta = '' + . esc_html($a['linkText'] ?? 'Learn More') . ''; } ob_start(); - if ( $img['html'] && $img['position'] === 'left' ) : ?> -'; - $visual_cls = 'platform-visual has-dashboard'; - } elseif ( $img_url ) { + $visual_cls = 'platform-visual has-dashboard'; + } + elseif ($img_url) { $img_style = 'width:' . $img_w . 'px;max-width:100%;height:auto;border-radius:var(--radius-sm);object-fit:contain;display:block;margin-inline:auto;'; - if ( $img_id ) { - $visual_html = wp_get_attachment_image( $img_id, 'full', false, [ 'style' => $img_style, 'alt' => $img_alt ] ); - } else { - $visual_html = '' . esc_attr( $img_alt ) . ''; + if ($img_id) { + $visual_html = wp_get_attachment_image($img_id, 'full', false, ['style' => $img_style, 'alt' => $img_alt]); + } + else { + $visual_html = '' . esc_attr($img_alt) . ''; } $visual_cls = 'platform-visual has-img'; - } elseif ( ! empty( $a['deviceAnim'] ) ) { + } + elseif (!empty($a['deviceAnim'])) { $da_screen = '
'; - $da = '
+ + + + + + + + + + + + + + + diff --git a/pages/features.php b/pages/features.php index e5a14bc..257909e 100644 --- a/pages/features.php +++ b/pages/features.php @@ -31,7 +31,7 @@ return <<<'ORIBI_SYNC_CONTENT' - + diff --git a/pages/pricing.php b/pages/pricing.php index d8236c9..e7fb71c 100644 --- a/pages/pricing.php +++ b/pages/pricing.php @@ -26,6 +26,12 @@ return <<<'ORIBI_SYNC_CONTENT' + + + + + + diff --git a/pages/resources.php b/pages/resources.php index 4cd0e84..4d2d64a 100644 --- a/pages/resources.php +++ b/pages/resources.php @@ -32,7 +32,13 @@ return <<<'ORIBI_SYNC_CONTENT' - + + + + + + + From 44e2ed7061af76ca64af0be0608d59e650f5ac92 Mon Sep 17 00:00:00 2001 From: Matt Batchelder Date: Fri, 27 Feb 2026 17:57:59 -0500 Subject: [PATCH 33/38] feat: Add digital signage animation to the intro section block. --- pages/about.php | 5 +- theme/assets/css/main.css | 116 +- theme/blocks/editor.js | 4111 +++++++++++++++++++------------------ theme/blocks/index.php | 19 +- 4 files changed, 2247 insertions(+), 2004 deletions(-) diff --git a/pages/about.php b/pages/about.php index c5e2a30..8409071 100644 --- a/pages/about.php +++ b/pages/about.php @@ -8,7 +8,7 @@ return <<<'ORIBI_SYNC_CONTENT' - + @@ -38,5 +38,4 @@ return <<<'ORIBI_SYNC_CONTENT' - -ORIBI_SYNC_CONTENT; +ORIBI_SYNC_CONTENT; \ No newline at end of file diff --git a/theme/assets/css/main.css b/theme/assets/css/main.css index e78faa7..3e6a8d9 100644 --- a/theme/assets/css/main.css +++ b/theme/assets/css/main.css @@ -3272,7 +3272,8 @@ p:last-child { margin-bottom: 0; } color: #fff; } /* When an image is set, remove the gradient box styling */ -.about-intro-visual.has-img { +.about-intro-visual.has-img, +.about-intro-visual.has-cloud-anim { background: none; box-shadow: none; aspect-ratio: unset; @@ -3284,6 +3285,119 @@ p:last-child { margin-bottom: 0; } .about-intro-visual { max-width: 280px; margin-inline: auto; } } +/* ── Digital Signage Animation ────────────────────────────── */ +.ds-anim-container { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + max-width: 400px; + margin: 0 auto; + padding: 2rem 0; + position: relative; + color: var(--color-primary); +} + +[data-theme="dark"] .ds-anim-container { + color: #fff; +} + +.ds-tv, .ds-cloud { + position: relative; + z-index: 2; + display: flex; + align-items: center; + justify-content: center; +} + +.ds-tv { + font-size: 5rem; + position: relative; +} + +.ds-tv-screen { + position: absolute; + top: 15%; + left: 10%; + width: 80%; + height: 60%; + background: rgba(var(--color-primary-rgb), 0.2); + border-radius: 4px; + animation: ds-screen-pulse 3s infinite alternate; +} + +[data-theme="dark"] .ds-tv-screen { + background: rgba(255, 255, 255, 0.15); +} + +@keyframes ds-screen-pulse { + 0% { opacity: 0.4; } + 100% { opacity: 1; } +} + +.ds-cloud { + font-size: 4.5rem; + animation: ds-float 4s ease-in-out infinite; +} + +@keyframes ds-float { + 0% { transform: translateY(0); } + 50% { transform: translateY(-8px); } + 100% { transform: translateY(0); } +} + +.ds-line { + flex-grow: 1; + height: 2px; + background: rgba(var(--color-primary-rgb), 0.2); + margin: 0 1.5rem; + position: relative; + display: flex; + align-items: center; + overflow: hidden; + border-radius: 2px; +} + +[data-theme="dark"] .ds-line { + background: rgba(255, 255, 255, 0.1); +} + +.ds-packet { + width: 12px; + height: 4px; + background: var(--color-primary); + border-radius: 4px; + position: absolute; + left: -20px; + box-shadow: 0 0 8px var(--color-primary); +} + +[data-theme="dark"] .ds-packet { + background: #fff; + box-shadow: 0 0 8px rgba(255, 255, 255, 0.8); +} + +.ds-packet-1 { animation: ds-travel 2s linear infinite; } +.ds-packet-2 { animation: ds-travel 2s linear infinite 0.6s; } +.ds-packet-3 { animation: ds-travel 2s linear infinite 1.2s; } + +@keyframes ds-travel { + 0% { left: -20px; opacity: 0; } + 10% { opacity: 1; } + 90% { opacity: 1; } + 100% { left: 100%; opacity: 0; } +} + +@media (prefers-reduced-motion: reduce) { + .ds-tv-screen, .ds-cloud, .ds-packet { + animation: none; + } + .ds-packet-1 { left: 20%; opacity: 1; } + .ds-packet-2 { left: 50%; opacity: 1; } + .ds-packet-3 { left: 80%; opacity: 1; } +} + + /* ── 13. Contact ────────────────────────────────────────────── */ .contact-layout { display: grid; diff --git a/theme/blocks/editor.js b/theme/blocks/editor.js index a8c8bdc..add7336 100644 --- a/theme/blocks/editor.js +++ b/theme/blocks/editor.js @@ -6,2050 +6,2167 @@ * Child blocks are dynamic (save -> null) with inline RichText editing. */ (function (wp) { -'use strict'; + '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; + 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; } + /* ── 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' + /* ── 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' } }; - 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 - }) - ) - ) - ) - ); + /* ── Shared icon attributes ──────────────────────────────────────────── */ + var CARD_ICON_ATTRS = { + icon: { type: 'string', default: '' }, + iconType: { type: 'string', default: 'emoji' }, + faIcon: { type: 'string', default: '' } }; -} -/** 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 } -}; + /** + * Build icon InspectorControls: a toggle that switches between emoji and + * Font Awesome mode, with the appropriate text input shown beneath it. + */ -/* ═══════════════════════════════════════════════════════════════════════ - STANDALONE BLOCKS (unchanged architecture) - ═══════════════════════════════════════════════════════════════════════ */ + /* ── 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'] + ]; -/* 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});} }) + /* ── 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') ) - ), - 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') + : 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]) ); - }), - 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 = '
'; - var DA_HTML = ''; - - var TS_MI = '
'; - var TS_COL = '
' + TS_MI + TS_MI + TS_MI + '
'; - var TS_HTML = ''; - - var NGD_ROW = '
'; - var NGD_ROWH = '
'; - var NGD_HTML = ''; - - var BD_SPLASH = '
'; - var BD_HDR = '
'; - var BD_HTML = ''; - - var DB_HTML = '
PerformanceAPICacheDBQueueWorkerRequests/secReadWriteUpdateDeleteTraffic TrendDistribution
'; - - 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; + function iconControls(a, s) { + var useFa = a.iconType === 'fontawesome'; 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(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') + ); + } + }) ), - 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' } }, + 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: ['oribi/trust-item'], - template: [['oribi/trust-item', {}]], + allowedBlocks: allowedBlocks, + template: defaultTemplate, 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' }, + /** Standard section attributes for JS. */ + var SECTION_ATTRS = { + align: { type: 'string', default: 'full' }, variant: { type: 'string', default: 'normal' }, - label: { type: 'string', default: '' }, + 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...' }) + 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('div', { className: 'faq-list' }, + el(PB, { title: 'Primary Button' }, + el(TC, { label: 'URL', value: a.primaryBtnUrl, onChange: function (v) { s({ primaryBtnUrl: v }); } }) + ), + el(PB, { title: 'Secondary Button', initialOpen: false }, + el(TC, { label: 'URL', value: a.secondaryBtnUrl, onChange: function (v) { s({ secondaryBtnUrl: v }); } }) + ) + ), + el('section', { className: 'hero' }, + el('div', { className: 'container hero-inner' }, + el('div', { className: 'hero-content' }, + el(RT, { + tagName: 'span', className: 'hero-label', value: a.label, + onChange: function (v) { s({ label: v }); }, placeholder: '\u25CF Label text', allowedFormats: [] + }), + el(RT, { + tagName: 'h1', className: 'hero-title', value: a.title, + onChange: function (v) { s({ title: v }); }, placeholder: 'Hero title...' + }), + el(RT, { + tagName: 'p', className: 'hero-description', value: a.description, + onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' + }), + el('div', { className: 'btn-group' }, + el(RT, { + tagName: 'span', className: 'btn btn-primary btn-lg', value: a.primaryBtnText, + onChange: function (v) { s({ primaryBtnText: v }); }, placeholder: 'Button', allowedFormats: [] + }), + el(RT, { + tagName: 'span', className: 'btn btn-ghost btn-lg', value: a.secondaryBtnText, + onChange: function (v) { s({ secondaryBtnText: v }); }, placeholder: 'Secondary button', allowedFormats: [] + }) + ), + el('div', { className: 'hero-stats' }, + el('div', null, + el(RT, { + tagName: 'div', className: 'hero-stat-value', value: a.stat1Value, + onChange: function (v) { s({ stat1Value: v }); }, placeholder: '\u2014', allowedFormats: [] + }), + el(RT, { + tagName: 'div', className: 'hero-stat-label', value: a.stat1Label, + onChange: function (v) { s({ stat1Label: v }); }, placeholder: 'Stat label', allowedFormats: [] + }) + ), + el('div', null, + el(RT, { + tagName: 'div', className: 'hero-stat-value', value: a.stat2Value, + onChange: function (v) { s({ stat2Value: v }); }, placeholder: '\u2014', allowedFormats: [] + }), + el(RT, { + tagName: 'div', className: 'hero-stat-label', value: a.stat2Label, + onChange: function (v) { s({ stat2Label: v }); }, placeholder: 'Stat label', allowedFormats: [] + }) + ) + ) + ), + el('div', { className: 'hero-visual' }, + el('div', { className: 'hero-devices' }, + el('div', { className: 'hero-device hero-device--laptop', style: { opacity: 1 } }, + el('div', { className: 'hero-device__frame' }, + el('div', { className: 'hero-device__screen' }, + el('div', { className: 'hero-device__screen-content' }, + el('div', { className: 'hero-device__app-bars' }, el('div'), el('div'), el('div'), el('div')) + ) + ), + el('div', { className: 'hero-device__base' }) + ), + el('ul', { className: 'hero-device__services' }, + el('li', { className: 'svc', style: { opacity: 1 } }, el('span', { className: 'svc__dot' }), ' Data Backup'), + el('li', { className: 'svc', style: { opacity: 1 } }, el('span', { className: 'svc__dot' }), ' Endpoint Security'), + el('li', { className: 'svc', style: { opacity: 1 } }, el('span', { className: 'svc__dot' }), ' Patch Management') + ) + ), + el('div', { className: 'hero-device hero-device--cloud', style: { opacity: 1 } }, + el('div', { className: 'hero-device__frame' }, + el('div', { className: 'hero-device__cloud-icon' }, + el('span', { className: 'hero-device__cloud-label' }, '365') + ) + ), + el('ul', { className: 'hero-device__services' }, + el('li', { className: 'svc', style: { opacity: 1 } }, el('span', { className: 'svc__dot' }), ' Email Protection'), + el('li', { className: 'svc', style: { opacity: 1 } }, el('span', { className: 'svc__dot' }), ' License Management'), + el('li', { className: 'svc', style: { opacity: 1 } }, el('span', { className: 'svc__dot' }), ' Cloud Backup') + ) + ), + el('div', { className: 'hero-device hero-device--desktop', style: { opacity: 1 } }, + el('div', { className: 'hero-device__frame' }, + el('div', { className: 'hero-device__screen' }, + el('div', { className: 'hero-device__screen-content' }, + el('div', { className: 'hero-device__dash-row' }, el('div', { className: 'hero-device__dash-card' }), el('div', { className: 'hero-device__dash-card' })), + el('div', { className: 'hero-device__dash-bar' }), + el('div', { className: 'hero-device__dash-bar hero-device__dash-bar--short' }) + ) + ), + el('div', { className: 'hero-device__stand' }), + el('div', { className: 'hero-device__stand-base' }) + ), + el('ul', { className: 'hero-device__services' }, + el('li', { className: 'svc', style: { opacity: 1 } }, el('span', { className: 'svc__dot' }), ' Network Monitoring'), + el('li', { className: 'svc', style: { opacity: 1 } }, el('span', { className: 'svc__dot' }), ' Threat Detection'), + el('li', { className: 'svc', style: { opacity: 1 } }, el('span', { className: 'svc__dot' }), ' Cloud Management') + ) + ), + el('div', { className: 'hero-device hero-device--phone', style: { opacity: 1 } }, + el('div', { className: 'hero-device__frame' }, + el('div', { className: 'hero-device__screen' }, + el('div', { className: 'hero-device__screen-content' }, + el('div', { className: 'hero-device__notif' }, + el('span', { className: 'hero-device__notif-icon' }, '\u2713'), + el('span', { className: 'hero-device__notif-text' }, 'Secure') + ) + ) + ) + ), + el('ul', { className: 'hero-device__services' }, + el('li', { className: 'svc', style: { opacity: 1 } }, el('span', { className: 'svc__dot' }), ' Mobile Security'), + el('li', { className: 'svc', style: { opacity: 1 } }, el('span', { className: 'svc__dot' }), ' Data Encryption') + ) + ) + ) + ) + ) + ) + ); + }, + save: function () { return null; } + }); + + /* 2. PAGE HERO ────────────────────────────────────────────────────────── */ + reg('oribi/page-hero', { + title: 'Oribi Page Hero', + icon: 'flag', + category: 'oribi', + supports: { align: ['full'], html: false }, + attributes: { + align: { type: 'string', default: 'full' }, + label: { type: 'string', default: '' }, + title: { type: 'string', default: '' }, + description: { type: 'string', default: '' }, + }, + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Settings' }, + el(TC, { label: 'Label (optional)', value: a.label, onChange: function (v) { s({ label: v }); } }) + ) + ), + el('section', { className: 'page-hero' }, + el('div', { className: 'container' }, + a.label ? el('span', { className: 'hero-label' }, a.label) : null, + el(RT, { tagName: 'h1', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Page title...' }), + el(RT, { tagName: 'p', className: 'lead', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }) + ) + ) + ); + }, + save: function () { return null; } + }); + + /* 5. CTA BANNER ───────────────────────────────────────────────────────── */ + reg('oribi/cta-banner', { + title: 'Oribi CTA Banner', + icon: 'megaphone', + category: 'oribi', + supports: { align: ['full'], html: false }, + attributes: { + align: { type: 'string', default: 'full' }, + heading: { type: 'string', default: '' }, + text: { type: 'string', default: '' }, + btnText: { type: 'string', default: '' }, + btnUrl: { type: 'string', default: '' }, + }, + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Button' }, + el(TC, { label: 'URL', value: a.btnUrl, onChange: function (v) { s({ btnUrl: v }); } }) + ) + ), + el('section', { className: 'cta-banner' }, + el('div', { className: 'container text-center' }, + el(RT, { tagName: 'h2', value: a.heading, onChange: function (v) { s({ heading: v }); }, placeholder: 'CTA heading...' }), + el(RT, { tagName: 'p', value: a.text, onChange: function (v) { s({ text: v }); }, placeholder: 'CTA text...' }), + el(RT, { tagName: 'span', className: 'btn btn-primary btn-lg', style: { background: '#fff', color: 'var(--color-primary)' }, value: a.btnText, onChange: function (v) { s({ btnText: v }); }, placeholder: 'Button text...' }) + ) + ) + ); + }, + save: function () { return null; } + }); + + /* 6. INTRO SECTION ────────────────────────────────────────────────────── */ + reg('oribi/intro-section', { + title: 'Oribi Intro Section', + icon: 'id', + category: 'oribi', + supports: { align: ['full'], html: false }, + attributes: { + align: { type: 'string', default: 'full' }, + variant: { type: 'string', default: 'normal' }, + label: { type: 'string', default: '' }, + heading: { type: 'string', default: '' }, + description: { type: 'string', default: '' }, + visual: { type: 'string', default: '' }, + reversed: { type: 'boolean', default: false }, + cloudAnim: { type: 'boolean', default: false }, + imgId: { type: 'number', default: 0 }, + imgUrl: { type: 'string', default: '' }, + imgAlt: { type: 'string', default: '' }, + imgWidth: { type: 'number', default: 280 }, + }, + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + var imgW = a.imgWidth || 280; + + var animHtml = null; + if (a.cloudAnim) { + animHtml = el('div', { className: 'ds-anim-container' }, + el('div', { className: 'ds-tv' }, + el('i', { className: 'fas fa-tv', 'aria-hidden': 'true' }), + el('div', { className: 'ds-tv-screen' }) + ), + el('div', { className: 'ds-line' }, + el('div', { className: 'ds-packet ds-packet-1' }), + el('div', { className: 'ds-packet ds-packet-2' }), + el('div', { className: 'ds-packet ds-packet-3' }) + ), + el('div', { className: 'ds-cloud' }, + el('i', { className: 'fas fa-cloud', 'aria-hidden': 'true' }) + ) + ); + } + + var visualContent = a.cloudAnim ? animHtml : (a.imgUrl + ? el('img', { src: a.imgUrl, style: { width: imgW + 'px', maxWidth: '100%', height: 'auto', borderRadius: '8px', objectFit: 'contain', display: 'block' } }) + : (a.visual || '\uD83D\uDCBB')); + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Settings' }, + el(SC, { + label: 'Background', value: a.variant, options: [ + { label: 'Normal', value: 'normal' }, { label: 'Alternate', value: 'alt' } + ], onChange: function (v) { s({ variant: v }); } + }), + el(TC, { label: 'Label', value: a.label, onChange: function (v) { s({ label: v }); } }), + el(TC, { label: 'Visual (emoji or text)', value: a.visual, onChange: function (v) { s({ visual: v }); } }), + el(TG, { label: 'Reversed layout', checked: a.reversed, onChange: function (v) { s({ reversed: v }); } }), + el(TG, { label: 'Cloud Server Animation', checked: a.cloudAnim, onChange: function (v) { s({ cloudAnim: v }); } }) + ), + el(PB, { title: 'Visual Image', initialOpen: false }, + el(MUC, null, + el(MU, { + onSelect: function (media) { s({ imgId: media.id, imgUrl: media.url, imgAlt: media.alt || '' }); }, + allowedTypes: ['image'], + value: a.imgId || 0, + render: function (ref) { + return el(Frag, null, + a.imgUrl ? el('div', { style: { marginBottom: '8px' } }, + el('img', { src: a.imgUrl, style: { maxWidth: '100%', height: 'auto', borderRadius: '4px', display: 'block', marginBottom: '6px' } }), + el(Btn, { variant: 'link', isDestructive: true, onClick: function () { s({ imgId: 0, imgUrl: '', imgAlt: '' }); } }, 'Remove image') + ) : null, + el(Btn, { onClick: ref.open, variant: 'secondary', __next40pxDefaultSize: true }, + a.imgUrl ? 'Replace image' : 'Select from Media Library') + ); + } + }) + ), + a.imgUrl ? el(RC, { + label: 'Width (px)', value: imgW, min: 50, max: 420, step: 4, + onChange: function (v) { s({ imgWidth: v }); } + }) : null + ) + ), + el('section', { className: a.variant === 'alt' ? 'section section-alt' : 'section' }, + el('div', { className: 'container' }, + el('div', { className: 'about-intro', style: a.reversed ? { direction: 'rtl' } : {} }, + el('div', { style: a.reversed ? { direction: 'ltr' } : {} }, + a.label ? el('span', { className: 'section-label' }, a.label) : null, + el(RT, { tagName: 'h2', style: { marginBottom: '1.5rem' }, value: a.heading, onChange: function (v) { s({ heading: v }); }, placeholder: 'Heading...' }), + el(RT, { tagName: 'p', className: 'lead', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }) + ), + el('div', { className: 'about-intro-visual' + (a.cloudAnim ? ' has-cloud-anim' : (a.imgUrl ? ' has-img' : '')), style: a.reversed ? { direction: 'ltr' } : {} }, visualContent) + ) + ) + ) + ); + }, + save: function () { return null; } + }); + + /* 8. CONTACT SECTION ──────────────────────────────────────────────────── */ + reg('oribi/contact-section', { + title: 'Oribi Contact', + icon: 'email', + category: 'oribi', + supports: { align: ['full'], html: false }, + attributes: { + align: { type: 'string', default: 'full' }, + heading: { type: 'string', default: "Let's Talk" }, + lead: { type: 'string', default: '' }, + email: { type: 'string', default: 'solutions@oribi-tech.com' }, + supportUrl: { type: 'string', default: '' }, + portalUrl: { type: 'string', default: '' }, + location: { type: 'string', default: 'Saratoga Springs, Upstate New York' }, + formHeading: { type: 'string', default: 'Want to Learn More?' }, + }, + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Contact Settings' }, + el(TC, { label: 'Email', value: a.email, onChange: function (v) { s({ email: v }); } }), + el(TC, { label: 'Support URL', value: a.supportUrl, onChange: function (v) { s({ supportUrl: v }); } }), + el(TC, { label: 'Portal URL', value: a.portalUrl, onChange: function (v) { s({ portalUrl: v }); } }), + el(TC, { label: 'Location', value: a.location, onChange: function (v) { s({ location: v }); } }), + el(TC, { label: 'Form Heading', value: a.formHeading, onChange: function (v) { s({ formHeading: v }); } }) + ) + ), + el('section', { className: 'section' }, + el('div', { className: 'container' }, + el('div', { className: 'contact-layout' }, + el('div', { className: 'contact-info' }, + el(RT, { tagName: 'h2', value: a.heading, onChange: function (v) { s({ heading: v }); }, placeholder: 'Heading...' }), + el(RT, { tagName: 'p', className: 'lead', value: a.lead, onChange: function (v) { s({ lead: v }); }, placeholder: 'Lead text...' }), + el('div', { className: 'contact-method' }, + el('div', { className: 'contact-method-icon' }, '\uD83D\uDCE7'), + el('div', null, el('div', { className: 'contact-method-label' }, 'Email Us'), el('div', { className: 'contact-method-value' }, a.email)) + ), + el('div', { className: 'contact-method' }, + el('div', { className: 'contact-method-icon' }, '\uD83C\uDFAB'), + el('div', null, el('div', { className: 'contact-method-label' }, 'Support'), el('div', { className: 'contact-method-value' }, 'Open a Support Ticket')) + ), + el('div', { className: 'contact-method' }, + el('div', { className: 'contact-method-icon' }, '\uD83C\uDF0E'), + el('div', null, el('div', { className: 'contact-method-label' }, 'Client Portal'), el('div', { className: 'contact-method-value' }, 'portal.oribi-tech.com')) + ), + el('div', { className: 'contact-method' }, + el('div', { className: 'contact-method-icon' }, '\uD83D\uDCCD'), + el('div', null, el('div', { className: 'contact-method-label' }, 'Location'), el('div', { className: 'contact-method-value' }, a.location)) + ) + ), + el('div', { className: 'contact-form-wrap' }, + el('h3', { style: { marginBottom: '1.5rem' } }, a.formHeading), + el('div', { style: { padding: '2rem', background: 'var(--color-bg-alt)', borderRadius: 'var(--radius-md)', textAlign: 'center', color: 'var(--color-text-muted)' } }, + 'Contact form renders on the live site' + ) + ) + ) + ) + ) + ); + }, + save: function () { return null; } + }); + + /* ═══════════════════════════════════════════════════════════════════════ + CHILD BLOCKS (each renders one item inside a parent) + ═══════════════════════════════════════════════════════════════════════ */ + + /* ── Feature Card ─────────────────────────────────────────────────────── */ + reg('oribi/feature-card', { + title: 'Feature Card', + icon: 'screenoptions', + category: 'oribi', + parent: ['oribi/feature-section'], + supports: { html: false, reusable: false }, + attributes: Object.assign({}, { + icon: { type: 'string', default: '' }, + iconType: { type: 'string', default: 'emoji' }, + faIcon: { type: 'string', default: '' }, + title: { type: 'string', default: '' }, + description: { type: 'string', default: '' }, + url: { type: 'string', default: '' }, + centered: { type: 'boolean', default: false } + }, CARD_IMAGE_ATTRS), + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + var imgPos = a.imgPosition || 'top'; + var imgPrev = cardImagePreview(a); + var centeredStyle = a.centered ? { marginInline: 'auto' } : {}; + + var cardPreview; + if (a.imgUrl && imgPos === 'left') { + cardPreview = el('div', { className: 'oribi-card img-left' }, + imgPrev, + el('div', { className: 'oribi-card-body' }, + el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Card title...' }), + el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Card description...' }) + ) + ); + } else if (a.imgUrl && imgPos === 'background') { + cardPreview = el('div', { className: 'oribi-card img-bg', style: { backgroundImage: 'url(' + a.imgUrl + ')', backgroundSize: 'cover', backgroundPosition: 'center', color: '#fff' } }, + el('div', { className: 'oribi-card-body' }, + iconPreview(a, 'feature-icon', centeredStyle), + el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Card title...' }), + el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Card description...' }) + ) + ); + } else { + var showIconPrev = !a.imgUrl || imgPos !== 'replace-icon'; + cardPreview = el('div', { className: 'oribi-card' + (a.centered ? ' text-center' : '') + (a.imgUrl ? ' img-' + imgPos : '') }, + a.imgUrl && (imgPos === 'top' || imgPos === 'replace-icon') ? imgPrev : null, + showIconPrev ? iconPreview(a, 'feature-icon', centeredStyle) : null, + el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Card title...' }), + el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Card description...' }) + ); + } + + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Card Settings' }, + iconControls(a, s), + el(TC, { label: 'URL (optional)', value: a.url || '', onChange: function (v) { s({ url: v }); } }), + el(TG, { label: 'Centered', checked: !!a.centered, onChange: function (v) { s({ centered: v }); } }) + ), + cardImageControls(a, s) + ), + cardPreview + ); + }, + save: function () { return null; } + }); + + /* ── Value Card ───────────────────────────────────────────────────────── */ + reg('oribi/value-card', { + title: 'Value Card', + icon: 'heart', + category: 'oribi', + parent: ['oribi/value-section'], + supports: { html: false, reusable: false }, + attributes: Object.assign({}, { + icon: { type: 'string', default: '' }, + iconType: { type: 'string', default: 'emoji' }, + faIcon: { type: 'string', default: '' }, + title: { type: 'string', default: '' }, + description: { type: 'string', default: '' } + }, CARD_IMAGE_ATTRS), + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + var imgPos = a.imgPosition || 'top'; + var imgPrev = cardImagePreview(a); + var showIconPrev = !a.imgUrl || imgPos !== 'replace-icon'; + + var body; + if (a.imgUrl && imgPos === 'left') { + body = el('div', { className: 'oribi-card value-card img-left' }, + imgPrev, + el('div', { className: 'oribi-card-body' }, + el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Title...' }), + el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }) + ) + ); + } else if (a.imgUrl && imgPos === 'background') { + body = el('div', { className: 'oribi-card value-card img-bg', style: { backgroundImage: 'url(' + a.imgUrl + ')', backgroundSize: 'cover', backgroundPosition: 'center', color: '#fff' } }, + el('div', { className: 'oribi-card-body' }, + showIconPrev ? iconPreview(a, 'value-icon') : null, + el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Title...' }), + el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }) + ) + ); + } else { + body = el('div', { className: 'oribi-card value-card' + (a.imgUrl ? ' img-' + imgPos : '') }, + a.imgUrl && (imgPos === 'top' || imgPos === 'replace-icon') ? imgPrev : null, + showIconPrev ? iconPreview(a, 'value-icon') : null, + el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Title...' }), + el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }) + ); + } + + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Card Settings' }, + iconControls(a, s) + ), + cardImageControls(a, s) + ), + body + ); + }, + save: function () { return null; } + }); + + /* ── Addon Card ───────────────────────────────────────────────────────── */ + reg('oribi/addon-card', { + title: 'Addon Card', + icon: 'plus-alt2', + category: 'oribi', + parent: ['oribi/addon-section'], + supports: { html: false, reusable: false }, + attributes: Object.assign({}, { + icon: { type: 'string', default: '' }, + iconType: { type: 'string', default: 'emoji' }, + faIcon: { type: 'string', default: '' }, + title: { type: 'string', default: '' }, + description: { type: 'string', default: '' }, + tag: { type: 'string', default: '' } + }, CARD_IMAGE_ATTRS), + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + var imgPos = a.imgPosition || 'top'; + var imgPrev = cardImagePreview(a); + var showIconPrev = !a.imgUrl || imgPos !== 'replace-icon'; + + var body; + if (a.imgUrl && imgPos === 'left') { + body = el('div', { className: 'oribi-card img-left' }, + imgPrev, + el('div', { className: 'oribi-card-body' }, + a.tag ? el('span', { className: 'addon-tag' }, a.tag) : null, + el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Title...' }), + el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }) + ) + ); + } else if (a.imgUrl && imgPos === 'background') { + body = el('div', { className: 'oribi-card img-bg', style: { backgroundImage: 'url(' + a.imgUrl + ')', backgroundSize: 'cover', backgroundPosition: 'center', color: '#fff' } }, + el('div', { className: 'oribi-card-body' }, + showIconPrev ? iconPreview(a, 'feature-icon') : null, + a.tag ? el('span', { className: 'addon-tag' }, a.tag) : null, + el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Title...' }), + el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }) + ) + ); + } else { + body = el('div', { className: 'oribi-card' + (a.imgUrl ? ' img-' + imgPos : '') }, + a.imgUrl && (imgPos === 'top' || imgPos === 'replace-icon') ? imgPrev : null, + showIconPrev ? iconPreview(a, 'feature-icon') : null, + a.tag ? el('span', { className: 'addon-tag' }, a.tag) : null, + el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Title...' }), + el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }) + ); + } + + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Card Settings' }, + iconControls(a, s), + el(TC, { label: 'Tag / Badge (optional)', value: a.tag || '', onChange: function (v) { s({ tag: v }); } }) + ), + cardImageControls(a, s) + ), + body + ); + }, + save: function () { return null; } + }); + + /* ── Image Card ───────────────────────────────────────────────────────── */ + reg('oribi/image-card', { + title: 'Image Card', + icon: 'format-image', + category: 'oribi', + parent: ['oribi/image-section'], + supports: { html: false, reusable: false }, + attributes: Object.assign({}, { + icon: { type: 'string', default: '' }, + iconType: { type: 'string', default: 'emoji' }, + faIcon: { type: 'string', default: '' }, + title: { type: 'string', default: '' }, + description: { type: 'string', default: '' }, + url: { type: 'string', default: '' } + }, CARD_IMAGE_ATTRS), + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + var imgPos = a.imgPosition || 'top'; + var imgPrev = cardImagePreview(a); + var showIconPrev = !a.imgUrl || imgPos !== 'replace-icon'; + + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Card Settings' }, + iconControls(a, s), + el(TC, { label: 'URL (optional)', value: a.url || '', onChange: function (v) { s({ url: v }); } }) + ), + cardImageControls(a, s) + ), + el('div', { className: 'oribi-card image-card' + (a.imgUrl ? ' img-' + imgPos : '') }, + a.imgUrl && (imgPos === 'top' || imgPos === 'replace-icon') ? imgPrev : null, + showIconPrev ? iconPreview(a, 'feature-icon') : null, + el('div', { className: 'oribi-card-body' }, + el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Title...' }), + el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }) + ) + ) + ); + }, + save: function () { return null; } + }); + + /* ── Stat Card ────────────────────────────────────────────────────────── */ + reg('oribi/stat-card', { + title: 'Stat Card', + icon: 'chart-bar', + category: 'oribi', + parent: ['oribi/stat-section'], + supports: { html: false, reusable: false }, + attributes: Object.assign({}, { + icon: { type: 'string', default: '' }, + iconType: { type: 'string', default: 'emoji' }, + faIcon: { type: 'string', default: '' }, + value: { type: 'string', default: '' }, + label: { type: 'string', default: '' }, + description: { type: 'string', default: '' } + }, CARD_IMAGE_ATTRS), + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + var imgPrev = cardImagePreview(a); + var imgPos = a.imgPosition || 'top'; + + var body; + if (a.imgUrl && imgPos === 'background') { + body = el('div', { className: 'oribi-card stat-card img-bg', style: { backgroundImage: 'url(' + a.imgUrl + ')', backgroundSize: 'cover', backgroundPosition: 'center', color: '#fff' } }, + el('div', { className: 'oribi-card-body' }, + iconPreview(a, 'feature-icon'), + el(RT, { tagName: 'div', className: 'stat-value', value: a.value, onChange: function (v) { s({ value: v }); }, placeholder: '99.9%' }), + el(RT, { tagName: 'div', className: 'stat-label', value: a.label, onChange: function (v) { s({ label: v }); }, placeholder: 'Label...' }), + el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description (optional)...' }) + ) + ); + } else { + body = el('div', { className: 'oribi-card stat-card' + (a.imgUrl ? ' img-' + imgPos : '') }, + (a.imgUrl && imgPos !== 'replace-icon') ? imgPrev : null, + iconPreview(a, 'feature-icon'), + el(RT, { tagName: 'div', className: 'stat-value', value: a.value, onChange: function (v) { s({ value: v }); }, placeholder: '99.9%' }), + el(RT, { tagName: 'div', className: 'stat-label', value: a.label, onChange: function (v) { s({ label: v }); }, placeholder: 'Label...' }), + el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description (optional)...' }) + ); + } + + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Card Settings' }, + iconControls(a, s) + ), + cardImageControls(a, s) + ), + body + ); + }, + save: function () { return null; } + }); + + /* ── Link Card ────────────────────────────────────────────────────────── */ + reg('oribi/link-card', { + title: 'Link Card', + icon: 'admin-links', + category: 'oribi', + parent: ['oribi/link-section'], + supports: { html: false, reusable: false }, + attributes: Object.assign({}, { + icon: { type: 'string', default: '' }, + iconType: { type: 'string', default: 'emoji' }, + faIcon: { type: 'string', default: '' }, + title: { type: 'string', default: '' }, + description: { type: 'string', default: '' }, + linkText: { type: 'string', default: '' }, + linkUrl: { type: 'string', default: '' } + }, CARD_IMAGE_ATTRS), + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + var imgPos = a.imgPosition || 'top'; + var imgPrev = cardImagePreview(a); + var showIconPrev = !a.imgUrl || imgPos !== 'replace-icon'; + + var body; + if (a.imgUrl && imgPos === 'left') { + body = el('div', { className: 'oribi-card link-card img-left' }, + imgPrev, + el('div', { className: 'oribi-card-body' }, + showIconPrev ? iconPreview(a, 'feature-icon') : null, + el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Title...' }), + el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }), + el(RT, { tagName: 'span', className: 'link-card-cta', value: a.linkText, onChange: function (v) { s({ linkText: v }); }, placeholder: 'Link text → ...' }) + ) + ); + } else if (a.imgUrl && imgPos === 'background') { + body = el('div', { className: 'oribi-card link-card img-bg', style: { backgroundImage: 'url(' + a.imgUrl + ')', backgroundSize: 'cover', backgroundPosition: 'center', color: '#fff' } }, + el('div', { className: 'oribi-card-body' }, + showIconPrev ? iconPreview(a, 'feature-icon') : null, + el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Title...' }), + el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }), + el(RT, { tagName: 'span', className: 'link-card-cta', value: a.linkText, onChange: function (v) { s({ linkText: v }); }, placeholder: 'Link text → ...' }) + ) + ); + } else { + body = el('div', { className: 'oribi-card link-card' + (a.imgUrl ? ' img-' + imgPos : '') }, + (a.imgUrl && (imgPos === 'top' || imgPos === 'replace-icon')) ? imgPrev : null, + showIconPrev ? iconPreview(a, 'feature-icon') : null, + el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Title...' }), + el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }), + el(RT, { tagName: 'span', className: 'link-card-cta', value: a.linkText, onChange: function (v) { s({ linkText: v }); }, placeholder: 'Link text → ...' }) + ); + } + + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Card Settings' }, + iconControls(a, s), + el(TC, { label: 'Link URL', value: a.linkUrl || '', onChange: function (v) { s({ linkUrl: v }); } }) + ), + cardImageControls(a, s) + ), + body + ); + }, + save: function () { return null; } + }); + + /* ── Pricing Card ─────────────────────────────────────────────────────── */ + reg('oribi/pricing-card', { + title: 'Pricing Card', + icon: 'money-alt', + category: 'oribi', + parent: ['oribi/pricing-section'], + supports: { html: false, reusable: false }, + attributes: { + icon: { type: 'string', default: '' }, + iconType: { type: 'string', default: 'emoji' }, + faIcon: { type: 'string', default: '' }, + name: { type: 'string', default: '' }, + tagline: { type: 'string', default: '' }, + price: { type: 'string', default: '' }, + pricePer: { type: 'string', default: '' }, + features: { type: 'array', default: [] }, + btnText: { type: 'string', default: 'Get Started' }, + btnUrl: { type: 'string', default: '/contact' }, + featured: { type: 'boolean', default: false }, + badge: { type: 'string', default: '' }, + imgId: { type: 'number', default: 0 }, + imgUrl: { type: 'string', default: '' }, + imgAlt: { type: 'string', default: '' }, + imgWidth: { type: 'number', default: 80 }, + }, + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + var features = a.features || []; + var imgW = a.imgWidth || 80; + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Card Settings' }, + iconControls(a, s), + el(TC, { label: 'Button URL', value: a.btnUrl, onChange: function (v) { s({ btnUrl: v }); } }), + el(TG, { label: 'Featured', checked: !!a.featured, onChange: function (v) { s({ featured: v }); } }), + a.featured ? el(TC, { label: 'Badge Text', value: a.badge, onChange: function (v) { s({ badge: v }); } }) : null + ), + el(PB, { title: 'Card Image', initialOpen: false }, + el(MUC, null, + el(MU, { + onSelect: function (media) { s({ imgId: media.id, imgUrl: media.url, imgAlt: media.alt || '' }); }, + allowedTypes: ['image'], + value: a.imgId || 0, + render: function (ref) { + return el(Frag, null, + a.imgUrl ? el('div', { style: { marginBottom: '8px' } }, + el('img', { src: a.imgUrl, style: { maxWidth: '100%', height: 'auto', borderRadius: '4px', display: 'block', marginBottom: '6px' } }), + el(Btn, { variant: 'link', isDestructive: true, onClick: function () { s({ imgId: 0, imgUrl: '', imgAlt: '' }); } }, 'Remove image') + ) : null, + el(Btn, { onClick: ref.open, variant: 'secondary', __next40pxDefaultSize: true }, + a.imgUrl ? 'Replace image' : 'Select from Media Library') + ); + } + }) + ), + a.imgUrl ? el(RC, { + label: 'Width (px)', value: imgW, min: 20, max: 400, step: 4, + onChange: function (v) { s({ imgWidth: v }); } + }) : null + ) + ), + el('div', { className: 'pricing-card' + (a.featured ? ' featured' : '') }, + a.featured && a.badge ? el(RT, { + tagName: 'span', className: 'pricing-badge', value: a.badge, + onChange: function (v) { s({ badge: v }); }, placeholder: 'Badge...' + }) : null, + a.imgUrl ? el('div', { style: { textAlign: 'center', marginBottom: '1.25rem' } }, + el('img', { src: a.imgUrl, style: { width: imgW + 'px', maxWidth: '100%', height: 'auto', borderRadius: '4px', objectFit: 'contain' } }) + ) : null, + iconPreview(a, 'feature-icon', { marginInline: 'auto' }), + el(RT, { + tagName: 'div', className: 'pricing-name', value: a.name, + onChange: function (v) { s({ name: v }); }, placeholder: 'Plan name...' + }), + el(RT, { + tagName: 'p', className: 'pricing-tagline', value: a.tagline, + onChange: function (v) { s({ tagline: v }); }, placeholder: 'Tagline...' + }), + a.price || a.pricePer ? el('div', { className: 'pricing-price' }, + el(RT, { + tagName: 'div', className: 'pricing-amount', value: a.price || '', + onChange: function (v) { s({ price: v }); }, placeholder: '$0' + }), + el(RT, { + tagName: 'div', className: 'pricing-per', value: a.pricePer || '', + onChange: function (v) { s({ pricePer: v }); }, placeholder: 'per screen / month' + }) + ) : el('div', { className: 'pricing-price' }, + el(RT, { + tagName: 'div', className: 'pricing-amount', value: '', + onChange: function (v) { s({ price: v }); }, placeholder: '$0' + }), + el(RT, { + tagName: 'div', className: 'pricing-per', value: '', + onChange: function (v) { s({ pricePer: v }); }, placeholder: 'per screen / month' + }) + ), + el('ul', { className: 'pricing-features' }, + features.map(function (f, fi) { + return el('li', { key: fi, style: { display: 'flex', alignItems: 'center', gap: '4px' } }, + el('span', { className: 'pricing-check' }, '\u2713'), + el(RT, { + tagName: 'span', style: { flex: 1, minWidth: 0 }, value: f, + onChange: function (v) { s({ features: arrSet(features, fi, v) }); }, placeholder: 'Feature...' + }), + el(Btn, { + isSmall: true, isDestructive: true, + style: { minWidth: '20px', padding: 0, height: '20px', flexShrink: 0 }, + onClick: function () { s({ features: arrRm(features, fi) }); } + }, '\u2715') + ); + }), + el('li', { style: { listStyle: 'none', marginTop: '4px' } }, + el(Btn, { + isSmall: true, variant: 'secondary', + onClick: function () { s({ features: arrAdd(features, '') }); } + }, '+ Feature') + ) + ), + el(RT, { + tagName: 'span', + className: 'btn ' + (a.featured ? 'btn-primary' : 'btn-outline'), + style: { width: '100%', justifyContent: 'center', cursor: 'text' }, + value: a.btnText || '', + onChange: function (v) { s({ btnText: v }); }, placeholder: 'Button text...' + }) + ) + ); + }, + save: function () { return null; } + }); + + /* ── Platform Row ─────────────────────────────────────────────────────── */ + reg('oribi/platform-row', { + title: 'Platform Row', + icon: 'slides', + category: 'oribi', + parent: ['oribi/platform-section'], + supports: { html: false, reusable: false }, + attributes: { + heading: { type: 'string', default: '' }, + description: { type: 'string', default: '' }, + btnText: { type: 'string', default: 'Learn More' }, + btnUrl: { type: 'string', default: '' }, + visual: { type: 'string', default: '' }, + reversed: { type: 'boolean', default: false }, + imgId: { type: 'number', default: 0 }, + imgUrl: { type: 'string', default: '' }, + imgAlt: { type: 'string', default: '' }, + imgWidth: { type: 'number', default: 300 }, + isDashboard: { type: 'boolean', default: false }, + deviceAnim: { type: 'boolean', default: false }, + tvStick: { type: 'boolean', default: false }, + cameraAnim: { type: 'boolean', default: false }, + neverGoesDark: { type: 'boolean', default: false }, + brandedAnim: { type: 'boolean', default: false }, + galleryIds: { type: 'array', default: [] }, + }, + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + var imgW = a.imgWidth || 300; + + /* ── Animation HTML strings (mirror PHP render, used via dangerouslySetInnerHTML) ── */ + var DA_SCREEN = '
'; + var DA_HTML = ''; + + var TS_MI = '
'; + var TS_COL = '
' + TS_MI + TS_MI + TS_MI + '
'; + var TS_HTML = ''; + + var NGD_ROW = '
'; + var NGD_ROWH = '
'; + var NGD_HTML = ''; + + var BD_SPLASH = '
'; + var BD_HDR = '
'; + var BD_HTML = ''; + + var DB_HTML = '
PerformanceAPICacheDBQueueWorkerRequests/secReadWriteUpdateDeleteTraffic TrendDistribution
'; + + 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/faq-item'], - template: [['oribi/faq-item', {}]], + allowedBlocks: ['oribi/platform-row'], + template: [['oribi/platform-row', {}]], templateLock: false }) ) ) - ) - ); - }, - save: function () { return el(IB.Content); } -}); + ); + }, + save: function () { return el(IB.Content); } + }); -/* FAQ ITEM (child) ─────────────────────────────────────────────────────── */ -reg('oribi/faq-item', { - title: 'Oribi FAQ Item', - icon: 'editor-help', - category: 'oribi', - parent: ['oribi/faq-section'], - supports: { html: false, reusable: false }, - attributes: { - question: { type: 'string', default: '' }, - answer: { type: 'string', default: '' }, - }, - edit: function (props) { - var a = props.attributes, s = props.setAttributes; - return el('details', { className: 'faq-item', open: true }, - el('summary', { className: 'faq-question' }, - el(RT, { tagName: 'span', value: a.question, - onChange: function(v){s({question:v});}, placeholder: 'Question...' }) - ), - el('div', { className: 'faq-answer' }, - el(RT, { tagName: 'p', value: a.answer, - onChange: function(v){s({answer:v});}, placeholder: 'Answer...' }) - ) - ); - }, - save: function () { return null; } -}); - -/* 11. COMPARISON TABLE (standalone) ───────────────────────────────────── */ -reg('oribi/comparison-table', { - title: 'Oribi Comparison Table', - icon: 'editor-table', - category: 'oribi', - supports: { align: ['full'], html: false }, - attributes: { - align: { type: 'string', default: 'full' }, - variant: { type: 'string', default: 'normal' }, - label: { type: 'string', default: '' }, - heading: { type: 'string', default: '' }, - lead: { type: 'string', default: '' }, - columns: { type: 'array', default: [] }, - rows: { type: 'array', default: [] }, - }, - edit: function (props) { - var a = props.attributes, s = props.setAttributes; - var cols = a.columns || []; - var rows = a.rows || []; - - /* ── Column helpers ─────────────────────────────────────── */ - function updateCol(idx, val) { - var c = cols.slice(); c[idx] = val; s({ columns: c }); - } - function addCol() { - var newRows = rows.map(function(r) { - if (r.group) return r; - return Object.assign({}, r, { values: (r.values || []).concat([false]) }); - }); - s({ columns: cols.concat(['Plan']), rows: newRows }); - } - function removeCol(idx) { - var c = cols.slice(); c.splice(idx, 1); - var newRows = rows.map(function(r) { - if (r.group) return r; - var v = (r.values || []).slice(); v.splice(idx, 1); - return Object.assign({}, r, { values: v }); - }); - s({ columns: c, rows: newRows }); - } - - /* ── Row helpers ────────────────────────────────────────── */ - function updateRow(idx, key, val) { - var r = rows.slice(); - r[idx] = Object.assign({}, r[idx]); - r[idx][key] = val; - s({ rows: r }); - } - function updateCell(ri, ci, val) { - var r = rows.slice(); - r[ri] = Object.assign({}, r[ri]); - var v = (r[ri].values || []).slice(); - v[ci] = val; - r[ri].values = v; - s({ rows: r }); - } - function toggleCell(ri, ci) { - var val = (rows[ri].values || [])[ci]; - updateCell(ri, ci, val === true ? false : val === false ? true : true); - } - function switchCellToText(ri, ci) { updateCell(ri, ci, ''); } - function addFeatureRow() { - var vals = cols.map(function() { return false; }); - s({ rows: rows.concat([{ feature: 'New feature', values: vals }]) }); - } - function addGroupRow() { - s({ rows: rows.concat([{ group: 'New Group' }]) }); - } - function removeRow(idx) { - var r = rows.slice(); r.splice(idx, 1); s({ rows: r }); - } - function moveRow(idx, dir) { - var t = idx + dir; - if (t < 0 || t >= rows.length) return; - var r = rows.slice(); - var tmp = r[idx]; r[idx] = r[t]; r[t] = tmp; - s({ rows: r }); - } - - /* ── Inline styles for editor controls ──────────────────── */ - var inputStyle = { width: '100%', padding: '4px 6px', border: '1px solid #ddd', borderRadius: '3px', fontSize: '13px', background: 'transparent', boxSizing: 'border-box' }; - var thInputStyle = Object.assign({}, inputStyle, { fontWeight: 600, textAlign: 'center' }); - var smallBtnStyle = { background: 'none', border: 'none', cursor: 'pointer', padding: '2px 4px', fontSize: '11px', lineHeight: 1, opacity: 0.6, verticalAlign: 'middle' }; - var rowCtrlStyle = { whiteSpace: 'nowrap', width: '1%', padding: '4px', verticalAlign: 'middle', border: 'none', background: 'transparent' }; - var cellBtnStyle = { background: 'none', border: '1px solid #ddd', borderRadius: '3px', cursor: 'pointer', padding: '2px 6px', fontSize: '13px', lineHeight: '1.4', margin: '0 1px' }; - - /* ── Render ─────────────────────────────────────────────── */ - return el(Frag, null, - el(IC, null, - el(PB, { title: 'Table Settings' }, - el(SC, { label: 'Background', value: a.variant, options: [ - { label: 'Normal', value: 'normal' }, { label: 'Alternate', value: 'alt' } - ], onChange: function(v){s({variant:v});} }), - el(TC, { label: 'Label', value: a.label, onChange: function(v){s({label:v});} }) + /* 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(PB, { title: 'Columns', initialOpen: false }, - cols.map(function(col, i) { - return el('div', { key: i, style: { display: 'flex', gap: '4px', marginBottom: '8px', alignItems: 'center' } }, - el(TC, { label: '', value: col, onChange: function(v){ updateCol(i, v); }, style: { flex: 1 } }), - el(Btn, { isDestructive: true, isSmall: true, onClick: function(){ removeCol(i); } }, '\u2715') - ); - }), - el(Btn, { isSecondary: true, isSmall: true, onClick: addCol }, '+ Add Column') - ), - el(PB, { title: 'Add Rows', initialOpen: false }, - el('div', { style: { display: 'flex', gap: '8px' } }, - el(Btn, { isSecondary: true, isSmall: true, onClick: addFeatureRow }, '+ Feature Row'), - el(Btn, { isSecondary: true, isSmall: true, onClick: addGroupRow }, '+ Group Row') + el('section', { className: '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 + ) + ) ) ) - ), - 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...' }) + ); + }, + save: function () { return el(IB.Content); } + }); + + /* 10. FAQ SECTION ─────────────────────────────────────────────────────── */ + reg('oribi/faq-section', { + title: 'Oribi FAQ Section', + icon: 'editor-help', + category: 'oribi', + supports: { align: ['full'], html: false }, + attributes: { + align: { type: 'string', default: 'full' }, + variant: { type: 'string', default: 'normal' }, + label: { type: 'string', default: '' }, + heading: { type: 'string', default: '' }, + lead: { type: 'string', default: '' }, + }, + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Section Settings' }, + el(SC, { + label: 'Background', value: a.variant, options: [ + { label: 'Normal', value: 'normal' }, { label: 'Alternate', value: 'alt' } + ], onChange: function (v) { s({ variant: v }); } + }), + el(TC, { label: 'Label', value: a.label, onChange: function (v) { s({ label: v }); } }) + ) + ), + el('section', { className: a.variant === 'alt' ? 'section section-alt' : 'section' }, + el('div', { className: 'container' }, + el('div', { className: 'section-header' }, + a.label ? el('span', { className: 'section-label' }, a.label) : null, + el(RT, { tagName: 'h2', value: a.heading, onChange: function (v) { s({ heading: v }); }, placeholder: 'FAQ heading...' }), + el(RT, { tagName: 'p', className: 'lead', value: a.lead, onChange: function (v) { s({ lead: v }); }, placeholder: 'Lead text...' }) + ), + el('div', { className: 'faq-list' }, + el(IB, { + allowedBlocks: ['oribi/faq-item'], + template: [['oribi/faq-item', {}]], + templateLock: false + }) + ) + ) + ) + ); + }, + save: function () { return el(IB.Content); } + }); + + /* FAQ ITEM (child) ─────────────────────────────────────────────────────── */ + reg('oribi/faq-item', { + title: 'Oribi FAQ Item', + icon: 'editor-help', + category: 'oribi', + parent: ['oribi/faq-section'], + supports: { html: false, reusable: false }, + attributes: { + question: { type: 'string', default: '' }, + answer: { type: 'string', default: '' }, + }, + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + return el('details', { className: 'faq-item', open: true }, + el('summary', { className: 'faq-question' }, + el(RT, { + tagName: 'span', value: a.question, + onChange: function (v) { s({ question: v }); }, placeholder: 'Question...' + }) + ), + el('div', { className: 'faq-answer' }, + el(RT, { + tagName: 'p', value: a.answer, + onChange: function (v) { s({ answer: v }); }, placeholder: 'Answer...' + }) + ) + ); + }, + save: function () { return null; } + }); + + /* 11. COMPARISON TABLE (standalone) ───────────────────────────────────── */ + reg('oribi/comparison-table', { + title: 'Oribi Comparison Table', + icon: 'editor-table', + category: 'oribi', + supports: { align: ['full'], html: false }, + attributes: { + align: { type: 'string', default: 'full' }, + variant: { type: 'string', default: 'normal' }, + label: { type: 'string', default: '' }, + heading: { type: 'string', default: '' }, + lead: { type: 'string', default: '' }, + columns: { type: 'array', default: [] }, + rows: { type: 'array', default: [] }, + }, + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + var cols = a.columns || []; + var rows = a.rows || []; + + /* ── Column helpers ─────────────────────────────────────── */ + function updateCol(idx, val) { + var c = cols.slice(); c[idx] = val; s({ columns: c }); + } + function addCol() { + var newRows = rows.map(function (r) { + if (r.group) return r; + return Object.assign({}, r, { values: (r.values || []).concat([false]) }); + }); + s({ columns: cols.concat(['Plan']), rows: newRows }); + } + function removeCol(idx) { + var c = cols.slice(); c.splice(idx, 1); + var newRows = rows.map(function (r) { + if (r.group) return r; + var v = (r.values || []).slice(); v.splice(idx, 1); + return Object.assign({}, r, { values: v }); + }); + s({ columns: c, rows: newRows }); + } + + /* ── Row helpers ────────────────────────────────────────── */ + function updateRow(idx, key, val) { + var r = rows.slice(); + r[idx] = Object.assign({}, r[idx]); + r[idx][key] = val; + s({ rows: r }); + } + function updateCell(ri, ci, val) { + var r = rows.slice(); + r[ri] = Object.assign({}, r[ri]); + var v = (r[ri].values || []).slice(); + v[ci] = val; + r[ri].values = v; + s({ rows: r }); + } + function toggleCell(ri, ci) { + var val = (rows[ri].values || [])[ci]; + updateCell(ri, ci, val === true ? false : val === false ? true : true); + } + function switchCellToText(ri, ci) { updateCell(ri, ci, ''); } + function addFeatureRow() { + var vals = cols.map(function () { return false; }); + s({ rows: rows.concat([{ feature: 'New feature', values: vals }]) }); + } + function addGroupRow() { + s({ rows: rows.concat([{ group: 'New Group' }]) }); + } + function removeRow(idx) { + var r = rows.slice(); r.splice(idx, 1); s({ rows: r }); + } + function moveRow(idx, dir) { + var t = idx + dir; + if (t < 0 || t >= rows.length) return; + var r = rows.slice(); + var tmp = r[idx]; r[idx] = r[t]; r[t] = tmp; + s({ rows: r }); + } + + /* ── Inline styles for editor controls ──────────────────── */ + var inputStyle = { width: '100%', padding: '4px 6px', border: '1px solid #ddd', borderRadius: '3px', fontSize: '13px', background: 'transparent', boxSizing: 'border-box' }; + var thInputStyle = Object.assign({}, inputStyle, { fontWeight: 600, textAlign: 'center' }); + var smallBtnStyle = { background: 'none', border: 'none', cursor: 'pointer', padding: '2px 4px', fontSize: '11px', lineHeight: 1, opacity: 0.6, verticalAlign: 'middle' }; + var rowCtrlStyle = { whiteSpace: 'nowrap', width: '1%', padding: '4px', verticalAlign: 'middle', border: 'none', background: 'transparent' }; + var cellBtnStyle = { background: 'none', border: '1px solid #ddd', borderRadius: '3px', cursor: 'pointer', padding: '2px 6px', fontSize: '13px', lineHeight: '1.4', margin: '0 1px' }; + + /* ── Render ─────────────────────────────────────────────── */ + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Table Settings' }, + el(SC, { + label: 'Background', value: a.variant, options: [ + { label: 'Normal', value: 'normal' }, { label: 'Alternate', value: 'alt' } + ], onChange: function (v) { s({ variant: v }); } + }), + el(TC, { label: 'Label', value: a.label, onChange: function (v) { s({ label: v }); } }) ), - el('div', { className: 'comparison-table-wrap' }, - el('table', { className: 'comparison-table' }, - el('thead', null, - el('tr', null, - el('th', { className: 'comparison-feature-col' }, 'Feature'), - cols.map(function(col, i) { - return el('th', { key: i }, - el('input', { type: 'text', value: col, style: thInputStyle, onChange: function(e){ updateCol(i, e.target.value); } }) - ); - }), - el('th', { style: rowCtrlStyle }) - ) - ), - el('tbody', null, - rows.map(function(row, ri) { - if (row.group) { - return el('tr', { key: ri, className: 'comparison-group-row' }, - el('td', { colSpan: cols.length + 1 }, - el('input', { type: 'text', value: row.group, style: Object.assign({}, inputStyle, { fontWeight: 700 }), onChange: function(e){ updateRow(ri, 'group', e.target.value); } }) - ), - el('td', { style: rowCtrlStyle }, - el('button', { style: smallBtnStyle, onClick: function(){ moveRow(ri, -1); }, title: 'Move up' }, '\u25B2'), - el('button', { style: smallBtnStyle, onClick: function(){ moveRow(ri, 1); }, title: 'Move down' }, '\u25BC'), - el('button', { style: Object.assign({}, smallBtnStyle, { color: '#b00' }), onClick: function(){ removeRow(ri); }, title: 'Remove row' }, '\u2715') - ) - ); - } - return el('tr', { key: ri }, - el('td', { className: 'comparison-feature-name' }, - el('input', { type: 'text', value: row.feature || '', style: inputStyle, placeholder: 'Feature name\u2026', onChange: function(e){ updateRow(ri, 'feature', e.target.value); } }) - ), - (row.values || []).map(function(val, ci) { - if (typeof val === 'boolean') { - return el('td', { key: ci, className: 'comparison-cell', style: { textAlign: 'center' } }, - el('button', { style: Object.assign({}, cellBtnStyle, { color: val ? '#2e7d32' : '#c62828' }), onClick: function(){ toggleCell(ri, ci); }, title: 'Toggle \u2713/\u2717' }, - val ? '\u2713' : '\u2717' - ), - el('button', { style: Object.assign({}, smallBtnStyle, { fontSize: '10px' }), onClick: function(){ switchCellToText(ri, ci); }, title: 'Switch to text' }, 'Aa') - ); - } - return el('td', { key: ci, className: 'comparison-cell' }, - el('div', { style: { display: 'flex', alignItems: 'center', gap: '2px' } }, - el('input', { type: 'text', value: String(val), style: Object.assign({}, inputStyle, { flex: 1, minWidth: '60px' }), placeholder: 'Value\u2026', onChange: function(e){ updateCell(ri, ci, e.target.value); } }), - el('button', { style: Object.assign({}, smallBtnStyle, { color: '#2e7d32' }), onClick: function(){ updateCell(ri, ci, true); }, title: 'Set as \u2713' }, '\u2713'), - el('button', { style: Object.assign({}, smallBtnStyle, { color: '#c62828' }), onClick: function(){ updateCell(ri, ci, false); }, title: 'Set as \u2717' }, '\u2717') - ) + el(PB, { title: 'Columns', initialOpen: false }, + cols.map(function (col, i) { + return el('div', { key: i, style: { display: 'flex', gap: '4px', marginBottom: '8px', alignItems: 'center' } }, + el(TC, { label: '', value: col, onChange: function (v) { updateCol(i, v); }, style: { flex: 1 } }), + el(Btn, { isDestructive: true, isSmall: true, onClick: function () { removeCol(i); } }, '\u2715') + ); + }), + el(Btn, { isSecondary: true, isSmall: true, onClick: addCol }, '+ Add Column') + ), + el(PB, { title: 'Add Rows', initialOpen: false }, + el('div', { style: { display: 'flex', gap: '8px' } }, + el(Btn, { isSecondary: true, isSmall: true, onClick: addFeatureRow }, '+ Feature Row'), + el(Btn, { isSecondary: true, isSmall: true, onClick: addGroupRow }, '+ Group Row') + ) + ) + ), + el('section', { className: a.variant === 'alt' ? 'section section-alt' : 'section' }, + el('div', { className: 'container' }, + el('div', { className: 'section-header' }, + a.label ? el('span', { className: 'section-label' }, a.label) : null, + el(RT, { tagName: 'h2', value: a.heading, onChange: function (v) { s({ heading: v }); }, placeholder: 'Table heading...' }), + el(RT, { tagName: 'p', className: 'lead', value: a.lead, onChange: function (v) { s({ lead: v }); }, placeholder: 'Lead text...' }) + ), + el('div', { className: 'comparison-table-wrap' }, + el('table', { className: 'comparison-table' }, + el('thead', null, + el('tr', null, + el('th', { className: 'comparison-feature-col' }, 'Feature'), + cols.map(function (col, i) { + return el('th', { key: i }, + el('input', { type: 'text', value: col, style: thInputStyle, onChange: function (e) { updateCol(i, e.target.value); } }) ); }), - el('td', { style: rowCtrlStyle }, - el('button', { style: smallBtnStyle, onClick: function(){ moveRow(ri, -1); }, title: 'Move up' }, '\u25B2'), - el('button', { style: smallBtnStyle, onClick: function(){ moveRow(ri, 1); }, title: 'Move down' }, '\u25BC'), - el('button', { style: Object.assign({}, smallBtnStyle, { color: '#b00' }), onClick: function(){ removeRow(ri); }, title: 'Remove row' }, '\u2715') - ) - ); + el('th', { style: rowCtrlStyle }) + ) + ), + el('tbody', null, + rows.map(function (row, ri) { + if (row.group) { + return el('tr', { key: ri, className: 'comparison-group-row' }, + el('td', { colSpan: cols.length + 1 }, + el('input', { type: 'text', value: row.group, style: Object.assign({}, inputStyle, { fontWeight: 700 }), onChange: function (e) { updateRow(ri, 'group', e.target.value); } }) + ), + el('td', { style: rowCtrlStyle }, + el('button', { style: smallBtnStyle, onClick: function () { moveRow(ri, -1); }, title: 'Move up' }, '\u25B2'), + el('button', { style: smallBtnStyle, onClick: function () { moveRow(ri, 1); }, title: 'Move down' }, '\u25BC'), + el('button', { style: Object.assign({}, smallBtnStyle, { color: '#b00' }), onClick: function () { removeRow(ri); }, title: 'Remove row' }, '\u2715') + ) + ); + } + return el('tr', { key: ri }, + el('td', { className: 'comparison-feature-name' }, + el('input', { type: 'text', value: row.feature || '', style: inputStyle, placeholder: 'Feature name\u2026', onChange: function (e) { updateRow(ri, 'feature', e.target.value); } }) + ), + (row.values || []).map(function (val, ci) { + if (typeof val === 'boolean') { + return el('td', { key: ci, className: 'comparison-cell', style: { textAlign: 'center' } }, + el('button', { style: Object.assign({}, cellBtnStyle, { color: val ? '#2e7d32' : '#c62828' }), onClick: function () { toggleCell(ri, ci); }, title: 'Toggle \u2713/\u2717' }, + val ? '\u2713' : '\u2717' + ), + el('button', { style: Object.assign({}, smallBtnStyle, { fontSize: '10px' }), onClick: function () { switchCellToText(ri, ci); }, title: 'Switch to text' }, 'Aa') + ); + } + return el('td', { key: ci, className: 'comparison-cell' }, + el('div', { style: { display: 'flex', alignItems: 'center', gap: '2px' } }, + el('input', { type: 'text', value: String(val), style: Object.assign({}, inputStyle, { flex: 1, minWidth: '60px' }), placeholder: 'Value\u2026', onChange: function (e) { updateCell(ri, ci, e.target.value); } }), + el('button', { style: Object.assign({}, smallBtnStyle, { color: '#2e7d32' }), onClick: function () { updateCell(ri, ci, true); }, title: 'Set as \u2713' }, '\u2713'), + el('button', { style: Object.assign({}, smallBtnStyle, { color: '#c62828' }), onClick: function () { updateCell(ri, ci, false); }, title: 'Set as \u2717' }, '\u2717') + ) + ); + }), + el('td', { style: rowCtrlStyle }, + el('button', { style: smallBtnStyle, onClick: function () { moveRow(ri, -1); }, title: 'Move up' }, '\u25B2'), + el('button', { style: smallBtnStyle, onClick: function () { moveRow(ri, 1); }, title: 'Move down' }, '\u25BC'), + el('button', { style: Object.assign({}, smallBtnStyle, { color: '#b00' }), onClick: function () { removeRow(ri); }, title: 'Remove row' }, '\u2715') + ) + ); + }) + ) + ), + el('div', { style: { display: 'flex', gap: '8px', marginTop: '12px', justifyContent: 'center' } }, + el(Btn, { isSecondary: true, isSmall: true, onClick: addFeatureRow }, '+ Feature Row'), + el(Btn, { isSecondary: true, isSmall: true, onClick: addGroupRow }, '+ Group Row'), + el(Btn, { isSecondary: true, isSmall: true, onClick: addCol }, '+ Column') + ) + ) + ) + ) + ); + }, + save: function () { return null; } + }); + + /* ═══════════════════════════════════════════════════════════════════════ + TEMPLATE-PART HELPER BLOCKS + ═══════════════════════════════════════════════════════════════════════ */ + + /* ═══════════════════════════════════════════════════════════════════════ + ANIMATED HERO BLOCKS (OTS Signs) + ═══════════════════════════════════════════════════════════════════════ */ + + /* ANIMATED HERO ───────────────────────────────────────────────────────── */ + reg('oribi/hero-animated', { + title: 'Animated Hero', + icon: 'admin-site-alt3', + category: 'oribi', + supports: { align: ['full'], html: false }, + attributes: { + align: { type: 'string', default: 'full' }, + label: { type: 'string', default: '' }, + title: { type: 'string', default: '' }, + highlightWord: { type: 'string', default: '' }, + description: { type: 'string', default: '' }, + primaryBtnText: { type: 'string', default: 'Get Started' }, + primaryBtnUrl: { type: 'string', default: '/contact' }, + secondaryBtnText: { type: 'string', default: '' }, + secondaryBtnUrl: { type: 'string', default: '' }, + stat1Value: { type: 'string', default: '' }, + stat1Label: { type: 'string', default: '' }, + stat2Value: { type: 'string', default: '' }, + stat2Label: { type: 'string', default: '' }, + stat3Value: { type: 'string', default: '' }, + stat3Label: { type: 'string', default: '' }, + }, + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + // Build particle elements for editor preview + var particles = []; + for (var i = 1; i <= 12; i++) { + particles.push(el('div', { key: 'p' + i, className: 'hero-particle hero-particle--' + i })); + } + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Highlight' }, + el(TC, { label: 'Word to highlight in title', value: a.highlightWord, onChange: function (v) { s({ highlightWord: v }); } }) + ), + el(PB, { title: 'Primary Button' }, + el(TC, { label: 'URL', value: a.primaryBtnUrl, onChange: function (v) { s({ primaryBtnUrl: v }); } }) + ), + el(PB, { title: 'Secondary Button', initialOpen: false }, + el(TC, { label: 'URL', value: a.secondaryBtnUrl, onChange: function (v) { s({ secondaryBtnUrl: v }); } }) + ) + ), + el('section', { className: 'hero hero-animated' }, + el('div', { className: 'hero-particles', 'aria-hidden': 'true' }, particles), + el('div', { className: 'hero-animated__glow' }), + el('div', { className: 'container hero-animated__inner' }, + el('div', { className: 'hero-animated__content' }, + el(RT, { + tagName: 'span', className: 'hero-label', value: a.label, + onChange: function (v) { s({ label: v }); }, placeholder: '\u25CF Label text', allowedFormats: [] + }), + el(RT, { + tagName: 'h1', className: 'hero-title', value: a.title, + onChange: function (v) { s({ title: v }); }, placeholder: 'Hero title...' + }), + el(RT, { + tagName: 'p', className: 'hero-description', value: a.description, + onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' + }), + el('div', { className: 'btn-group' }, + el(RT, { + tagName: 'span', className: 'btn btn-primary btn-lg', value: a.primaryBtnText, + onChange: function (v) { s({ primaryBtnText: v }); }, placeholder: 'Button', allowedFormats: [] + }), + el(RT, { + tagName: 'span', className: 'btn btn-ghost btn-lg', value: a.secondaryBtnText, + onChange: function (v) { s({ secondaryBtnText: v }); }, placeholder: 'Secondary button', allowedFormats: [] }) - ) - ), - el('div', { style: { display: 'flex', gap: '8px', marginTop: '12px', justifyContent: 'center' } }, - el(Btn, { isSecondary: true, isSmall: true, onClick: addFeatureRow }, '+ Feature Row'), - el(Btn, { isSecondary: true, isSmall: true, onClick: addGroupRow }, '+ Group Row'), - el(Btn, { isSecondary: true, isSmall: true, onClick: addCol }, '+ Column') - ) - ) - ) - ) - ); - }, - save: function () { return null; } -}); - -/* ═══════════════════════════════════════════════════════════════════════ - TEMPLATE-PART HELPER BLOCKS - ═══════════════════════════════════════════════════════════════════════ */ - -/* ═══════════════════════════════════════════════════════════════════════ - ANIMATED HERO BLOCKS (OTS Signs) - ═══════════════════════════════════════════════════════════════════════ */ - -/* ANIMATED HERO ───────────────────────────────────────────────────────── */ -reg('oribi/hero-animated', { - title: 'Animated Hero', - icon: 'admin-site-alt3', - category: 'oribi', - supports: { align: ['full'], html: false }, - attributes: { - align: { type: 'string', default: 'full' }, - label: { type: 'string', default: '' }, - title: { type: 'string', default: '' }, - highlightWord: { type: 'string', default: '' }, - description: { type: 'string', default: '' }, - primaryBtnText: { type: 'string', default: 'Get Started' }, - primaryBtnUrl: { type: 'string', default: '/contact' }, - secondaryBtnText: { type: 'string', default: '' }, - secondaryBtnUrl: { type: 'string', default: '' }, - stat1Value: { type: 'string', default: '' }, - stat1Label: { type: 'string', default: '' }, - stat2Value: { type: 'string', default: '' }, - stat2Label: { type: 'string', default: '' }, - stat3Value: { type: 'string', default: '' }, - stat3Label: { type: 'string', default: '' }, - }, - edit: function (props) { - var a = props.attributes, s = props.setAttributes; - // Build particle elements for editor preview - var particles = []; - for (var i = 1; i <= 12; i++) { - particles.push(el('div', { key: 'p' + i, className: 'hero-particle hero-particle--' + i })); - } - return el(Frag, null, - el(IC, null, - el(PB, { title: 'Highlight' }, - el(TC, { label: 'Word to highlight in title', value: a.highlightWord, onChange: function(v){s({highlightWord:v});} }) - ), - el(PB, { title: 'Primary Button' }, - el(TC, { label: 'URL', value: a.primaryBtnUrl, onChange: function(v){s({primaryBtnUrl:v});} }) - ), - el(PB, { title: 'Secondary Button', initialOpen: false }, - el(TC, { label: 'URL', value: a.secondaryBtnUrl, onChange: function(v){s({secondaryBtnUrl:v});} }) - ) - ), - el('section', { className: 'hero hero-animated' }, - el('div', { className: 'hero-particles', 'aria-hidden': 'true' }, particles), - el('div', { className: 'hero-animated__glow' }), - el('div', { className: 'container hero-animated__inner' }, - el('div', { className: 'hero-animated__content' }, - el(RT, { tagName: 'span', className: 'hero-label', value: a.label, - onChange: function(v){s({label:v});}, placeholder: '\u25CF Label text', allowedFormats: [] }), - el(RT, { tagName: 'h1', className: 'hero-title', value: a.title, - onChange: function(v){s({title:v});}, placeholder: 'Hero title...' }), - el(RT, { tagName: 'p', className: 'hero-description', value: a.description, - onChange: function(v){s({description:v});}, placeholder: 'Description...' }), - el('div', { className: 'btn-group' }, - el(RT, { tagName: 'span', className: 'btn btn-primary btn-lg', value: a.primaryBtnText, - onChange: function(v){s({primaryBtnText:v});}, placeholder: 'Button', allowedFormats: [] }), - el(RT, { tagName: 'span', className: 'btn btn-ghost btn-lg', value: a.secondaryBtnText, - onChange: function(v){s({secondaryBtnText:v});}, placeholder: 'Secondary button', allowedFormats: [] }) - ), - el('div', { className: 'hero-stats hero-stats--three' }, - el('div', null, - el(RT, { tagName: 'div', className: 'hero-stat-value', value: a.stat1Value, - onChange: function(v){s({stat1Value:v});}, placeholder: '\u2014', allowedFormats: [] }), - el(RT, { tagName: 'div', className: 'hero-stat-label', value: a.stat1Label, - onChange: function(v){s({stat1Label:v});}, placeholder: 'Stat label', allowedFormats: [] }) ), - el('div', null, - el(RT, { tagName: 'div', className: 'hero-stat-value', value: a.stat2Value, - onChange: function(v){s({stat2Value:v});}, placeholder: '\u2014', allowedFormats: [] }), - el(RT, { tagName: 'div', className: 'hero-stat-label', value: a.stat2Label, - onChange: function(v){s({stat2Label:v});}, placeholder: 'Stat label', allowedFormats: [] }) - ), - el('div', null, - el(RT, { tagName: 'div', className: 'hero-stat-value', value: a.stat3Value, - onChange: function(v){s({stat3Value:v});}, placeholder: '\u2014', allowedFormats: [] }), - el(RT, { tagName: 'div', className: 'hero-stat-label', value: a.stat3Label, - onChange: function(v){s({stat3Label:v});}, placeholder: 'Stat label', allowedFormats: [] }) + el('div', { className: 'hero-stats hero-stats--three' }, + el('div', null, + el(RT, { + tagName: 'div', className: 'hero-stat-value', value: a.stat1Value, + onChange: function (v) { s({ stat1Value: v }); }, placeholder: '\u2014', allowedFormats: [] + }), + el(RT, { + tagName: 'div', className: 'hero-stat-label', value: a.stat1Label, + onChange: function (v) { s({ stat1Label: v }); }, placeholder: 'Stat label', allowedFormats: [] + }) + ), + el('div', null, + el(RT, { + tagName: 'div', className: 'hero-stat-value', value: a.stat2Value, + onChange: function (v) { s({ stat2Value: v }); }, placeholder: '\u2014', allowedFormats: [] + }), + el(RT, { + tagName: 'div', className: 'hero-stat-label', value: a.stat2Label, + onChange: function (v) { s({ stat2Label: v }); }, placeholder: 'Stat label', allowedFormats: [] + }) + ), + el('div', null, + el(RT, { + tagName: 'div', className: 'hero-stat-value', value: a.stat3Value, + onChange: function (v) { s({ stat3Value: v }); }, placeholder: '\u2014', allowedFormats: [] + }), + el(RT, { + tagName: 'div', className: 'hero-stat-label', value: a.stat3Label, + onChange: function (v) { s({ stat3Label: v }); }, placeholder: 'Stat label', allowedFormats: [] + }) + ) ) ) ) ) - ) - ); - }, - save: function () { return null; } -}); - -/* ANIMATED PAGE HERO ──────────────────────────────────────────────────── */ -reg('oribi/page-hero-animated', { - title: 'Animated Page Hero', - icon: 'flag', - category: 'oribi', - supports: { align: ['full'], html: false }, - attributes: { - align: { type: 'string', default: 'full' }, - label: { type: 'string', default: '' }, - title: { type: 'string', default: '' }, - description: { type: 'string', default: '' }, - }, - edit: function (props) { - var a = props.attributes, s = props.setAttributes; - var particles = []; - for (var i = 1; i <= 8; i++) { - particles.push(el('div', { key: 'p' + i, className: 'hero-particle hero-particle--' + i })); - } - return el(Frag, null, - el(IC, null, - el(PB, { title: 'Settings' }, - el(TC, { label: 'Label (optional)', value: a.label, onChange: function(v){s({label:v});} }) - ) - ), - el('section', { className: 'page-hero page-hero-animated' }, - el('div', { className: 'hero-particles', 'aria-hidden': 'true' }, particles), - el('div', { className: 'hero-animated__glow' }), - el('div', { className: 'hero-overlay' }), - el('div', { className: 'container' }, - a.label ? el('span', { className: 'hero-label' }, a.label) : null, - el(RT, { tagName: 'h1', value: a.title, onChange: function(v){s({title:v});}, placeholder: 'Page title...' }), - el(RT, { tagName: 'p', className: 'lead', value: a.description, onChange: function(v){s({description:v});}, placeholder: 'Description...' }) - ) - ) - ); - }, - save: function () { return null; } -}); - -/* ═══════════════════════════════════════════════════════════════════════ - TEMPLATE-PART HELPER BLOCKS - ═══════════════════════════════════════════════════════════════════════ */ - -reg('oribi/site-header', { - title: 'Oribi Site Header', - icon: 'admin-home', - category: 'oribi', - supports: { html: false, multiple: false, reusable: false }, - edit: function () { - return el('div', { - style: { background: '#111111', color: '#fff', padding: '20px 24px', borderRadius: '8px', display: 'flex', alignItems: 'center', justifyContent: 'space-between' } + ); }, - el('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } }, - el('strong', { style: { fontSize: '1.2rem' } }, 'Oribi'), - el('span', { style: { fontSize: '1.2rem', fontWeight: 300 } }, 'Tech') - ), - el('div', { style: { display: 'flex', gap: '1.5rem', fontSize: '.9rem', opacity: 0.7 } }, - el('span', null, 'Services'), - el('span', null, 'About'), - el('span', null, 'FAQ'), - el('span', null, 'Contact') - ) - ); - }, - save: function () { return null; } -}); + 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' } + /* ANIMATED PAGE HERO ──────────────────────────────────────────────────── */ + reg('oribi/page-hero-animated', { + title: 'Animated Page Hero', + icon: 'flag', + category: 'oribi', + supports: { align: ['full'], html: false }, + attributes: { + align: { type: 'string', default: 'full' }, + label: { type: 'string', default: '' }, + title: { type: 'string', default: '' }, + description: { type: 'string', default: '' }, }, - 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; } -}); + edit: function (props) { + var a = props.attributes, s = props.setAttributes; + var particles = []; + for (var i = 1; i <= 8; i++) { + particles.push(el('div', { key: 'p' + i, className: 'hero-particle hero-particle--' + i })); + } + return el(Frag, null, + el(IC, null, + el(PB, { title: 'Settings' }, + el(TC, { label: 'Label (optional)', value: a.label, onChange: function (v) { s({ label: v }); } }) + ) + ), + el('section', { className: 'page-hero page-hero-animated' }, + el('div', { className: 'hero-particles', 'aria-hidden': 'true' }, particles), + el('div', { className: 'hero-animated__glow' }), + el('div', { className: 'hero-overlay' }), + el('div', { className: 'container' }, + a.label ? el('span', { className: 'hero-label' }, a.label) : null, + el(RT, { tagName: 'h1', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Page title...' }), + el(RT, { tagName: 'p', className: 'lead', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Description...' }) + ) + ) + ); + }, + save: function () { return null; } + }); + + /* ═══════════════════════════════════════════════════════════════════════ + TEMPLATE-PART HELPER BLOCKS + ═══════════════════════════════════════════════════════════════════════ */ + + reg('oribi/site-header', { + title: 'Oribi Site Header', + icon: 'admin-home', + category: 'oribi', + supports: { html: false, multiple: false, reusable: false }, + edit: function () { + return el('div', { + style: { background: '#111111', color: '#fff', padding: '20px 24px', borderRadius: '8px', display: 'flex', alignItems: 'center', justifyContent: 'space-between' } + }, + el('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } }, + el('strong', { style: { fontSize: '1.2rem' } }, 'Oribi'), + el('span', { style: { fontSize: '1.2rem', fontWeight: 300 } }, 'Tech') + ), + el('div', { style: { display: 'flex', gap: '1.5rem', fontSize: '.9rem', opacity: 0.7 } }, + el('span', null, 'Services'), + el('span', null, 'About'), + el('span', null, 'FAQ'), + el('span', null, 'Contact') + ) + ); + }, + save: function () { return null; } + }); + + reg('oribi/site-footer', { + title: 'Oribi Site Footer', + icon: 'admin-home', + category: 'oribi', + supports: { html: false, multiple: false, reusable: false }, + edit: function () { + return el('div', { + style: { background: '#111111', color: '#fff', padding: '24px', borderRadius: '8px', textAlign: 'center' } + }, + el('strong', { style: { fontSize: '1.1rem' } }, 'OTS Theme - Site Footer'), + el('p', { style: { opacity: 0.5, margin: '8px 0 0', fontSize: '.85rem' } }, 'Brand · Service Links · Company Links · Connect · Copyright') + ); + }, + save: function () { return null; } + }); })(window.wp); diff --git a/theme/blocks/index.php b/theme/blocks/index.php index 27aef9e..051abc8 100644 --- a/theme/blocks/index.php +++ b/theme/blocks/index.php @@ -339,6 +339,7 @@ add_action('init', function () { 'description' => ['type' => 'string', 'default' => ''], 'visual' => ['type' => 'string', 'default' => ''], 'reversed' => ['type' => 'boolean', 'default' => false], + 'cloudAnim' => ['type' => 'boolean', 'default' => false], 'imgId' => ['type' => 'number', 'default' => 0], 'imgUrl' => ['type' => 'string', 'default' => ''], 'imgAlt' => ['type' => 'string', 'default' => ''], @@ -972,7 +973,20 @@ function oribi_render_intro_section($a)

-
>
+
> + '; + echo '
'; + echo '
'; + echo '
'; + echo '
'; + } + else { + echo wp_kses_post($a['visual']); + } +?> +
@@ -1650,8 +1664,7 @@ function oribi_render_camera_animation() 0:00 - -HTML; +HTML; } function oribi_render_platform_row($a) From 9f415320dee2dae4b4c229e26c938bc5e5989d94 Mon Sep 17 00:00:00 2001 From: Matt Batchelder Date: Fri, 27 Feb 2026 19:06:27 -0500 Subject: [PATCH 34/38] syntax --- pages/about.php | 3 ++- theme/blocks/index.php | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pages/about.php b/pages/about.php index 8409071..c98d5ae 100644 --- a/pages/about.php +++ b/pages/about.php @@ -38,4 +38,5 @@ return <<<'ORIBI_SYNC_CONTENT' -ORIBI_SYNC_CONTENT; \ No newline at end of file +ORIBI_SYNC_CONTENT; +ORIBI_SYNC_CONTENT; diff --git a/theme/blocks/index.php b/theme/blocks/index.php index 051abc8..72e11d4 100644 --- a/theme/blocks/index.php +++ b/theme/blocks/index.php @@ -1664,9 +1664,11 @@ function oribi_render_camera_animation() 0:00 -HTML; + +HTML; } + function oribi_render_platform_row($a) { $rev = !empty($a['reversed']) ? ' reverse' : ''; From fa6dce039bd52a6df118d8ef829f1ece2dcb10e9 Mon Sep 17 00:00:00 2001 From: Matt Batchelder Date: Mon, 16 Mar 2026 20:29:15 -0400 Subject: [PATCH 35/38] Add new animations for retail, corporate, education, outdoor, live data, healthcare, transit, and fitness sectors - Extend block attributes to include new animation options in index.php - Implement animation rendering logic for each sector in oribi_render_platform_row function - Enqueue new JavaScript file for solutions page animations in enqueue.php - Create solutions-animator.js to handle live data KPI and transit board animations --- pages/solutions.php | 16 +- theme/assets/css/main.css | 1478 +++++++++++++++++++++++++ theme/assets/js/solutions-animator.js | 291 +++++ theme/blocks/index.php | 358 ++++++ theme/inc/enqueue.php | 9 + 5 files changed, 2144 insertions(+), 8 deletions(-) create mode 100644 theme/assets/js/solutions-animator.js diff --git a/pages/solutions.php b/pages/solutions.php index e052496..3547c07 100644 --- a/pages/solutions.php +++ b/pages/solutions.php @@ -11,18 +11,18 @@ return <<<'ORIBI_SYNC_CONTENT' - + - + - + - + - - - - + + + + diff --git a/theme/assets/css/main.css b/theme/assets/css/main.css index 3e6a8d9..7385153 100644 --- a/theme/assets/css/main.css +++ b/theme/assets/css/main.css @@ -6262,3 +6262,1481 @@ p:last-child { margin-bottom: 0; } .hosp-menu__desc { display: none; } .hosp-menu__price { font-size: 14px; } } + +/* ═══════════════════════════════════════════════════════════════ + RETAIL PROMO ANIMATION (.platform-visual.has-retail) + TV cycling 3 slides: Flash Sale → New In Store → Member Rewards (9s) + ═══════════════════════════════════════════════════════════════ */ + +.platform-visual.has-retail { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.retail-stage { + width: 100%; + max-width: 520px; + margin: 0 auto; +} + +.retail-tv { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; +} + +.retail-tv__body { + width: 100%; + background: #111; + border: 5px solid #1a1a1a; + border-radius: 8px 8px 4px 4px; + outline: 2px solid #000; + padding: 4px; + box-shadow: 0 14px 48px rgba(0,0,0,0.6), inset 0 1px 0 rgba(255,255,255,0.06); +} + +.retail-tv__screen { + width: 100%; + aspect-ratio: 16/9; + background: #0a0a0a; + border-radius: 2px; + overflow: hidden; + position: relative; +} + +.retail-tv__feet { + display: flex; + justify-content: space-between; + width: 60%; + max-width: 300px; +} + +.retail-tv__foot { + width: 12px; + height: 8px; + background: #111; + border: 1px solid #000; + border-radius: 0 0 4px 4px; +} + +.retail-slides { + position: relative; + width: 100%; + height: 100%; +} + +.retail-slide { + position: absolute; + inset: 0; + opacity: 0; + animation: retail-fade 9s infinite; + display: flex; + align-items: stretch; +} + +.retail-slide--sale { animation-delay: 0s; } +.retail-slide--new { animation-delay: 3s; } +.retail-slide--loyalty { animation-delay: 6s; } + +.retail-promo { + width: 100%; + padding: 20px 28px; + display: flex; + flex-direction: column; + justify-content: center; + gap: 10px; +} + +/* Sale slide */ +.retail-slide--sale .retail-promo { background: linear-gradient(135deg, #1a0a0a 0%, #2a0d0d 100%); } +/* New arrivals slide */ +.retail-slide--new .retail-promo { background: linear-gradient(135deg, #0a0f1a 0%, #0d1a2a 100%); } +/* Loyalty slide */ +.retail-slide--loyalty .retail-promo { background: linear-gradient(135deg, #0a1a0d 0%, #0d2a12 100%); } + +.retail-promo__eyebrow { + font-size: 10px; + text-transform: uppercase; + letter-spacing: 1.5px; + color: rgba(255,255,255,0.5); +} + +.retail-promo__headline { + font-size: 28px; + font-weight: 800; + color: #fff; + line-height: 1.1; +} + +.retail-promo__badge { + display: inline-block; + background: var(--color-primary); + color: #fff; + font-size: 13px; + font-weight: 700; + padding: 4px 12px; + border-radius: 4px; + width: fit-content; +} + +.retail-promo__items { + display: flex; + flex-direction: column; + gap: 6px; + margin-top: 4px; +} + +.retail-promo__item { + display: flex; + align-items: center; + gap: 8px; + color: rgba(255,255,255,0.8); + font-size: 13px; +} + +.retail-promo__dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--color-primary); + flex-shrink: 0; +} + +.retail-promo__cta { + font-size: 11px; + color: rgba(255,255,255,0.4); + text-transform: uppercase; + letter-spacing: 1px; + margin-top: 4px; +} + +/* New arrivals swatches */ +.retail-promo__grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 8px; + margin: 4px 0; +} + +.retail-promo__swatch { + aspect-ratio: 1; + border-radius: 4px; + animation: retail-swatch-pulse 3s ease-in-out infinite; +} +.retail-promo__swatch--a { background: #c8a882; animation-delay: 0s; } +.retail-promo__swatch--b { background: #4a7c59; animation-delay: 0.4s; } +.retail-promo__swatch--c { background: #2c4a8a; animation-delay: 0.8s; } +.retail-promo__swatch--d { background: #8a2c2c; animation-delay: 1.2s; } + +.retail-promo__sub { + font-size: 12px; + color: rgba(255,255,255,0.5); +} + +/* Loyalty points */ +.retail-promo__points { + display: flex; + align-items: baseline; + gap: 10px; +} + +.retail-promo__pts-val { + font-size: 48px; + font-weight: 900; + color: var(--color-accent); + line-height: 1; +} + +.retail-promo__pts-lbl { + font-size: 15px; + color: rgba(255,255,255,0.8); +} + +.retail-promo__bar { + height: 8px; + background: rgba(255,255,255,0.12); + border-radius: 4px; + overflow: hidden; +} + +.retail-promo__bar-fill { + height: 100%; + width: 72%; + background: var(--color-accent); + border-radius: 4px; + animation: retail-bar-grow 2s ease-out infinite alternate; +} + +@keyframes retail-fade { + 0%, 2% { opacity: 0; transform: translateY(10px); } + 5%, 30% { opacity: 1; transform: translateY(0); } + 33%, 100% { opacity: 0; transform: translateY(-10px); } +} + +@keyframes retail-swatch-pulse { + 0%, 100% { opacity: 0.7; transform: scale(1); } + 50% { opacity: 1; transform: scale(1.04); } +} + +@keyframes retail-bar-grow { + from { width: 50%; } + to { width: 82%; } +} + +@media (prefers-reduced-motion: reduce) { + .retail-slide { animation: none; opacity: 0; } + .retail-slide--sale { opacity: 1; } + .retail-promo__swatch { animation: none; } + .retail-promo__bar-fill { animation: none; } +} + +@media (max-width: 640px) { + .retail-promo { padding: 14px 18px; gap: 7px; } + .retail-promo__headline { font-size: 20px; } + .retail-promo__pts-val { font-size: 36px; } +} + +/* ═══════════════════════════════════════════════════════════════ + CORPORATE MEETING ROOM PANEL (.platform-visual.has-corporate) + ═══════════════════════════════════════════════════════════════ */ + +.platform-visual.has-corporate { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.corp-stage { + width: 100%; + max-width: 400px; + margin: 0 auto; +} + +.corp-panel { + background: #0f1117; + border: 1px solid #1e2435; + border-radius: 12px; + padding: 24px 28px; + display: flex; + flex-direction: column; + gap: 20px; + box-shadow: 0 20px 60px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.04); + color: #fff; +} + +.corp-panel__header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.corp-panel__room { + font-size: 20px; + font-weight: 700; + color: #fff; +} + +.corp-panel__status { + display: flex; + align-items: center; + gap: 6px; + font-size: 12px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + padding: 4px 10px; + border-radius: 20px; +} + +.corp-panel__status--busy { + background: rgba(239,68,68,0.15); + color: #f87171; + border: 1px solid rgba(239,68,68,0.3); +} + +.corp-panel__dot { + width: 7px; + height: 7px; + border-radius: 50%; + background: #ef4444; + animation: corp-dot-pulse 1.8s ease-in-out infinite; +} + +.corp-panel__meeting { + border-left: 3px solid var(--color-primary); + padding-left: 14px; + display: flex; + flex-direction: column; + gap: 4px; +} + +.corp-panel__meeting-name { + font-size: 16px; + font-weight: 600; + color: #fff; +} + +.corp-panel__meeting-time { + font-size: 13px; + color: rgba(255,255,255,0.5); + font-family: monospace; +} + +.corp-panel__organiser { + font-size: 12px; + color: rgba(255,255,255,0.35); +} + +/* Timeline bar */ +.corp-panel__timeline { + display: flex; + flex-direction: column; + gap: 6px; +} + +.corp-panel__tl-track { + height: 8px; + background: rgba(255,255,255,0.08); + border-radius: 4px; + position: relative; + overflow: hidden; +} + +.corp-panel__tl-fill { + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 42%; + background: linear-gradient(90deg, var(--color-primary), #ff6b35); + border-radius: 4px; + animation: corp-tl-progress 12s linear infinite; +} + +.corp-panel__tl-now { + position: absolute; + top: -2px; + bottom: -2px; + left: 42%; + width: 2px; + background: #fff; + border-radius: 2px; + animation: corp-tl-now 12s linear infinite; +} + +.corp-panel__tl-labels { + display: flex; + justify-content: space-between; + font-size: 10px; + color: rgba(255,255,255,0.3); + font-family: monospace; +} + +/* Next meeting */ +.corp-panel__next { + background: rgba(255,255,255,0.04); + border-radius: 8px; + padding: 12px 14px; + display: flex; + flex-direction: column; + gap: 3px; +} + +.corp-panel__next-lbl { + font-size: 10px; + text-transform: uppercase; + letter-spacing: 1px; + color: rgba(255,255,255,0.35); +} + +.corp-panel__next-name { + font-size: 14px; + font-weight: 500; + color: rgba(255,255,255,0.8); +} + +.corp-panel__next-time { + font-size: 12px; + color: rgba(255,255,255,0.4); + font-family: monospace; +} + +/* Teams indicator */ +.corp-panel__teams { + display: flex; + align-items: center; + gap: 8px; + font-size: 12px; + color: rgba(255,255,255,0.45); +} + +.corp-panel__teams-icon { + width: 18px; + height: 18px; + background: #4a90d9; + border-radius: 4px; + flex-shrink: 0; +} + +@keyframes corp-dot-pulse { + 0%, 100% { opacity: 1; transform: scale(1); } + 50% { opacity: 0.4; transform: scale(0.7); } +} + +@keyframes corp-tl-progress { + 0% { width: 35%; } + 100% { width: 55%; } +} + +@keyframes corp-tl-now { + 0% { left: 35%; } + 100% { left: 55%; } +} + +@media (prefers-reduced-motion: reduce) { + .corp-panel__dot { animation: none; } + .corp-panel__tl-fill { animation: none; } + .corp-panel__tl-now { animation: none; } +} + +@media (max-width: 640px) { + .corp-panel { padding: 18px 20px; gap: 16px; } + .corp-panel__room { font-size: 16px; } + .corp-panel__meeting-name { font-size: 14px; } +} + +/* ═══════════════════════════════════════════════════════════════ + EDUCATION SCHEDULE BOARD (.platform-visual.has-education) + ═══════════════════════════════════════════════════════════════ */ + +.platform-visual.has-education { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.edu-stage { + width: 100%; + max-width: 480px; + margin: 0 auto; +} + +.edu-board { + background: #0d1117; + border: 1px solid #1e2435; + border-radius: 10px; + overflow: hidden; + box-shadow: 0 20px 60px rgba(0,0,0,0.5); + color: #fff; +} + +.edu-board__header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 14px 20px; + background: linear-gradient(135deg, #1a1f2e 0%, #141928 100%); + border-bottom: 1px solid rgba(255,255,255,0.06); +} + +.edu-board__title { + font-size: 15px; + font-weight: 700; + color: #fff; +} + +.edu-board__date { + font-size: 11px; + color: rgba(255,255,255,0.4); + font-family: monospace; +} + +.edu-board__rows { + display: flex; + flex-direction: column; +} + +.edu-board__row { + display: grid; + grid-template-columns: 56px 1fr 70px auto; + align-items: center; + gap: 12px; + padding: 10px 20px; + border-bottom: 1px solid rgba(255,255,255,0.04); + transition: background 0.2s; +} + +.edu-row--done { + opacity: 0.35; +} + +.edu-row--now { + background: rgba(76,175,80,0.08); + border-left: 3px solid var(--color-accent); + padding-left: 17px; + animation: edu-row-pulse 3s ease-in-out infinite; +} + +.edu-row__time { + font-size: 12px; + color: rgba(255,255,255,0.45); + font-family: monospace; +} + +.edu-row__subject { + font-size: 13px; + font-weight: 500; + color: rgba(255,255,255,0.85); +} + +.edu-row--now .edu-row__subject { + color: #fff; + font-weight: 600; +} + +.edu-row__room { + font-size: 11px; + color: rgba(255,255,255,0.35); + text-align: right; +} + +.edu-row__badge { + font-size: 10px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.5px; + background: var(--color-accent); + color: #fff; + padding: 2px 7px; + border-radius: 4px; +} + +/* Emergency alert bar */ +.edu-board__alert { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 20px; + background: rgba(245,158,11,0.12); + border-top: 1px solid rgba(245,158,11,0.25); + animation: edu-alert-glow 2.5s ease-in-out infinite; +} + +.edu-alert__icon { + width: 20px; + height: 20px; + border-radius: 50%; + background: rgba(245,158,11,0.3); + border: 1px solid #f59e0b; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + font-weight: 800; + color: #f59e0b; + flex-shrink: 0; + line-height: 20px; + text-align: center; +} + +.edu-alert__txt { + font-size: 12px; + color: #fbbf24; +} + +@keyframes edu-row-pulse { + 0%, 100% { background: rgba(76,175,80,0.08); } + 50% { background: rgba(76,175,80,0.14); } +} + +@keyframes edu-alert-glow { + 0%, 100% { background: rgba(245,158,11,0.10); } + 50% { background: rgba(245,158,11,0.18); } +} + +@media (prefers-reduced-motion: reduce) { + .edu-row--now { animation: none; } + .edu-board__alert { animation: none; } +} + +@media (max-width: 640px) { + .edu-board__row { grid-template-columns: 46px 1fr auto; gap: 8px; padding: 9px 14px; } + .edu-row__room { display: none; } + .edu-row__subject { font-size: 12px; } + .edu-board__header { padding: 12px 14px; } +} + +/* ═══════════════════════════════════════════════════════════════ + OUTDOOR MARKETPLACE BOARD (.platform-visual.has-outdoor) + ═══════════════════════════════════════════════════════════════ */ + +.platform-visual.has-outdoor { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.outdoor-stage { + width: 100%; + max-width: 520px; + margin: 0 auto; +} + +.outdoor-board { + background: #1a1a1a; + border: 6px solid #222; + border-radius: 8px; + outline: 2px solid #000; + box-shadow: 0 16px 60px rgba(0,0,0,0.65), inset 0 1px 0 rgba(255,255,255,0.06); + position: relative; +} + +.outdoor-board__screen { + aspect-ratio: 16/7; + overflow: hidden; + background: #090909; + border-radius: 2px; + position: relative; +} + +.outdoor-board__bezel { + height: 10px; + background: linear-gradient(180deg, #1a1a1a 0%, #111 100%); + border-top: 1px solid #0a0a0a; +} + +.outdoor-slides { + position: relative; + width: 100%; + height: 100%; +} + +.outdoor-slide { + position: absolute; + inset: 0; + opacity: 0; + animation: outdoor-fade 8s infinite; +} + +.outdoor-slide--info { animation-delay: 0s; } +.outdoor-slide--directory { animation-delay: 4s; } + +/* Market info slide */ +.outdoor-info { + height: 100%; + padding: 18px 24px; + background: linear-gradient(135deg, #0d1a0d 0%, #0a1a10 100%); + display: flex; + flex-direction: column; + gap: 12px; + color: #fff; +} + +.outdoor-info__header { + display: flex; + justify-content: space-between; + align-items: flex-start; +} + +.outdoor-info__name { + font-size: 20px; + font-weight: 800; + color: #fff; +} + +.outdoor-info__weather { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 2px; +} + +.outdoor-info__temp { + font-size: 24px; + font-weight: 700; + color: #fbbf24; +} + +.outdoor-info__cond { + font-size: 10px; + color: rgba(255,255,255,0.5); +} + +.outdoor-info__details { + display: flex; + flex-direction: column; + gap: 7px; +} + +.outdoor-info__row { + display: flex; + align-items: center; + gap: 8px; + font-size: 13px; + color: rgba(255,255,255,0.8); +} + +.outdoor-info__icon { + width: 16px; + height: 16px; + border-radius: 3px; + background: rgba(76,175,80,0.3); + border: 1px solid rgba(76,175,80,0.5); + flex-shrink: 0; +} + +/* Directory slide */ +.outdoor-dir { + height: 100%; + padding: 18px 24px; + background: linear-gradient(135deg, #0d0d1a 0%, #0a0a1a 100%); + display: flex; + flex-direction: column; + gap: 12px; + color: #fff; +} + +.outdoor-dir__title { + font-size: 16px; + font-weight: 700; + color: #fff; + border-bottom: 1px solid rgba(255,255,255,0.08); + padding-bottom: 8px; +} + +.outdoor-dir__grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; + flex: 1; + align-content: start; +} + +.outdoor-dir__cell { + background: rgba(255,255,255,0.05); + border: 1px solid rgba(255,255,255,0.08); + border-radius: 6px; + padding: 8px 10px; + display: flex; + flex-direction: column; + gap: 3px; +} + +.outdoor-dir__zone { + font-size: 10px; + font-weight: 700; + color: var(--color-accent); + font-family: monospace; + text-transform: uppercase; +} + +.outdoor-dir__cat { + font-size: 12px; + color: rgba(255,255,255,0.7); +} + +@keyframes outdoor-fade { + 0%, 2% { opacity: 0; transform: scale(0.98); } + 8%, 45% { opacity: 1; transform: scale(1); } + 50%, 100% { opacity: 0; transform: scale(0.98); } +} + +@media (prefers-reduced-motion: reduce) { + .outdoor-slide { animation: none; opacity: 0; } + .outdoor-slide--info { opacity: 1; } +} + +@media (max-width: 640px) { + .outdoor-info { padding: 12px 16px; } + .outdoor-dir { padding: 12px 16px; } + .outdoor-info__name { font-size: 16px; } +} + +/* ═══════════════════════════════════════════════════════════════ + LIVE DATA / OPS BOARD (.platform-visual.has-live-data) + KPI counters ticked by solutions-animator.js + ═══════════════════════════════════════════════════════════════ */ + +.platform-visual.has-live-data { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.ld-stage { + width: 100%; + max-width: 480px; + margin: 0 auto; +} + +.ld-board { + background: #0a0d14; + border: 1px solid #1a2035; + border-radius: 10px; + overflow: hidden; + box-shadow: 0 20px 60px rgba(0,0,0,0.6); + color: #fff; +} + +.ld-board__header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 18px; + background: rgba(255,255,255,0.03); + border-bottom: 1px solid rgba(255,255,255,0.06); +} + +.ld-board__title { + font-size: 13px; + font-weight: 600; + color: rgba(255,255,255,0.7); + text-transform: uppercase; + letter-spacing: 0.8px; + font-size: 11px; +} + +.ld-board__live { + display: flex; + align-items: center; + gap: 5px; + font-size: 10px; + font-weight: 700; + color: #f87171; + text-transform: uppercase; + letter-spacing: 1px; +} + +.ld-board__live-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: #ef4444; + animation: ld-live-pulse 1.4s ease-in-out infinite; +} + +.ld-kpis { + display: grid; + grid-template-columns: repeat(4, 1fr); + border-bottom: 1px solid rgba(255,255,255,0.06); +} + +.ld-kpi { + padding: 14px 12px; + border-right: 1px solid rgba(255,255,255,0.06); + display: flex; + flex-direction: column; + gap: 4px; +} + +.ld-kpi:last-child { border-right: none; } + +.ld-kpi__label { + font-size: 9px; + text-transform: uppercase; + letter-spacing: 0.6px; + color: rgba(255,255,255,0.35); +} + +.ld-kpi__value { + font-size: 18px; + font-weight: 700; + color: #fff; + font-family: monospace; + transition: color 0.2s; +} + +.ld-kpi__trend::after { + display: block; + font-size: 10px; + margin-top: 2px; +} + +.ld-kpi--up .ld-kpi__value { color: #4ade80; } +.ld-kpi--down .ld-kpi__value { color: #f87171; } +.ld-kpi--up .ld-kpi__trend::after { content: '▲'; color: #4ade80; } +.ld-kpi--down .ld-kpi__trend::after { content: '▼'; color: #f87171; } +.ld-kpi--neutral .ld-kpi__trend::after { content: '—'; color: rgba(255,255,255,0.3); } + +/* Sparkline chart area */ +.ld-chart { + padding: 12px 18px 8px; + border-bottom: 1px solid rgba(255,255,255,0.06); +} + +.ld-sparkline { + width: 100%; + height: 60px; + display: block; +} + +.ld-chart__label { + font-size: 10px; + color: rgba(255,255,255,0.25); + margin-top: 4px; +} + +/* Service status row */ +.ld-status { + display: flex; + align-items: center; + gap: 16px; + padding: 10px 18px; + flex-wrap: wrap; +} + +.ld-svc { + display: flex; + align-items: center; + gap: 5px; + font-size: 11px; + color: rgba(255,255,255,0.55); +} + +.ld-svc__dot { + width: 7px; + height: 7px; + border-radius: 50%; + flex-shrink: 0; +} + +.ld-svc--ok .ld-svc__dot { background: #4ade80; box-shadow: 0 0 6px #4ade80; } +.ld-svc--warn .ld-svc__dot { background: #fbbf24; box-shadow: 0 0 6px #fbbf24; animation: ld-warn-pulse 1.5s ease-in-out infinite; } +.ld-svc--err .ld-svc__dot { background: #f87171; box-shadow: 0 0 6px #f87171; animation: ld-warn-pulse 0.8s ease-in-out infinite; } + +@keyframes ld-live-pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.3; } +} + +@keyframes ld-warn-pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.4; } +} + +@media (max-width: 640px) { + .ld-kpis { grid-template-columns: repeat(2, 1fr); } + .ld-kpi__value { font-size: 15px; } + .ld-status { gap: 10px; padding: 8px 14px; } +} + +/* ═══════════════════════════════════════════════════════════════ + HEALTHCARE QUEUE BOARD (.platform-visual.has-healthcare) + ═══════════════════════════════════════════════════════════════ */ + +.platform-visual.has-healthcare { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.hc-stage { + width: 100%; + max-width: 460px; + margin: 0 auto; +} + +.hc-board { + background: #0a1419; + border: 1px solid #162030; + border-radius: 10px; + overflow: hidden; + box-shadow: 0 20px 60px rgba(0,0,0,0.55); + color: #fff; +} + +.hc-board__header { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 18px; + background: #0d1c2a; + border-bottom: 1px solid rgba(255,255,255,0.07); +} + +.hc-board__logo { + width: 28px; + height: 28px; + border-radius: 6px; + background: linear-gradient(135deg, #0ea5e9, #38bdf8); + flex-shrink: 0; +} + +.hc-board__title { + font-size: 15px; + font-weight: 600; + color: #fff; +} + +/* Now serving hero */ +.hc-now { + padding: 20px 18px 16px; + text-align: center; + border-bottom: 1px solid rgba(255,255,255,0.06); + background: linear-gradient(135deg, rgba(14,165,233,0.06) 0%, rgba(56,189,248,0.03) 100%); +} + +.hc-now__lbl { + font-size: 11px; + text-transform: uppercase; + letter-spacing: 1.5px; + color: rgba(255,255,255,0.4); + margin-bottom: 6px; +} + +.hc-now__number { + font-size: 40px; + font-weight: 900; + color: #38bdf8; + font-family: monospace; + letter-spacing: 4px; + animation: hc-number-flash 3.5s ease-in-out infinite; +} + +.hc-now__counter { + font-size: 12px; + color: rgba(255,255,255,0.5); + margin-top: 4px; +} + +/* Counter rows */ +.hc-counters { + display: flex; + flex-direction: column; + border-bottom: 1px solid rgba(255,255,255,0.06); +} + +.hc-counter { + display: grid; + grid-template-columns: 32px 1fr auto auto; + align-items: center; + gap: 10px; + padding: 10px 18px; + border-bottom: 1px solid rgba(255,255,255,0.04); + font-size: 12px; +} + +.hc-counter:last-child { border-bottom: none; } + +.hc-counter--active { } +.hc-counter--closed { opacity: 0.3; } + +.hc-counter__id { + font-size: 11px; + font-weight: 700; + color: rgba(255,255,255,0.4); + font-family: monospace; +} + +.hc-counter__doctor { + color: rgba(255,255,255,0.75); + font-size: 12px; +} + +.hc-counter__ticket { + font-size: 13px; + font-weight: 700; + color: #38bdf8; + font-family: monospace; + min-width: 36px; + text-align: right; +} + +.hc-counter__wait { + font-size: 11px; + color: rgba(255,255,255,0.35); + min-width: 50px; + text-align: right; +} + +.hc-board__footer { + padding: 10px 18px; + font-size: 11px; + color: rgba(255,255,255,0.3); + text-align: center; + background: rgba(255,255,255,0.02); +} + +@keyframes hc-number-flash { + 0%, 100% { opacity: 1; color: #38bdf8; } + 10% { opacity: 0.5; color: #7dd3fc; } + 20% { opacity: 1; color: #38bdf8; } +} + +@media (prefers-reduced-motion: reduce) { + .hc-now__number { animation: none; } +} + +@media (max-width: 640px) { + .hc-now__number { font-size: 28px; } + .hc-counter { grid-template-columns: 28px 1fr auto; gap: 8px; } + .hc-counter__wait { display: none; } +} + +/* ═══════════════════════════════════════════════════════════════ + TRANSIT DEPARTURE BOARD (.platform-visual.has-transit) + Split-flap flip characters; rows cycled by solutions-animator.js + ═══════════════════════════════════════════════════════════════ */ + +.platform-visual.has-transit { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.transit-stage { + width: 100%; + max-width: 500px; + margin: 0 auto; +} + +.transit-board { + background: #111; + border: 1px solid #1a1a1a; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 20px 60px rgba(0,0,0,0.7); + font-family: monospace; +} + +.transit-board__header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 16px; + background: #1a1a1a; + border-bottom: 1px solid rgba(255,255,255,0.06); +} + +.transit-board__title { + font-size: 14px; + font-weight: 700; + color: #f5f5a0; + letter-spacing: 1px; + text-transform: uppercase; +} + +.transit-board__clock { + font-size: 18px; + font-weight: 700; + color: #f5f5a0; + letter-spacing: 2px; +} + +.transit-board__cols { + display: grid; + grid-template-columns: 56px 1fr 64px 80px; + gap: 0; + padding: 6px 16px; + background: #161616; + border-bottom: 1px solid rgba(255,255,255,0.08); +} + +.transit-col-hd { + font-size: 9px; + text-transform: uppercase; + letter-spacing: 1px; + color: rgba(255,255,255,0.3); +} + +.transit-rows { + display: flex; + flex-direction: column; +} + +.transit-row { + display: grid; + grid-template-columns: 56px 1fr 64px 80px; + align-items: center; + padding: 7px 16px; + border-bottom: 1px solid rgba(255,255,255,0.04); + gap: 0; + transition: background 0.3s; +} + +.transit-row:last-child { border-bottom: none; } + +.transit-cell { + font-size: 13px; + color: #f5f5a0; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.transit-cell--time { + font-weight: 700; + letter-spacing: 1px; +} + +.transit-cell--plat { + text-align: center; + color: rgba(245,245,160,0.7); +} + +.transit-cell--status { } + +.transit-status--on-time { color: #4ade80; } +.transit-status--delayed { color: #f87171; animation: transit-delayed-blink 2s step-end infinite; } +.transit-status--cancelled { color: #f87171; text-decoration: line-through; } + +/* Split-flap character flip animation */ +.transit-flap { + display: inline-block; + animation: transit-flap-flip 0s; +} + +.transit-flap.is-flipping { + animation: transit-flap-flip 0.25s ease-in-out forwards; +} + +@keyframes transit-flap-flip { + 0% { transform: scaleY(1); } + 40% { transform: scaleY(0); opacity: 0.5; } + 60% { transform: scaleY(0); opacity: 0.5; } + 100% { transform: scaleY(1); } +} + +@keyframes transit-delayed-blink { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.4; } +} + +@media (prefers-reduced-motion: reduce) { + .transit-flap.is-flipping { animation: none; } + .transit-status--delayed { animation: none; } +} + +@media (max-width: 640px) { + .transit-board__cols, + .transit-row { grid-template-columns: 50px 1fr 52px; } + .transit-cell--plat { display: none; } + .transit-col-hd:nth-child(3) { display: none; } + .transit-cell { font-size: 11px; } +} + +/* ═══════════════════════════════════════════════════════════════ + FITNESS CLASS SCHEDULE (.platform-visual.has-fitness) + ═══════════════════════════════════════════════════════════════ */ + +.platform-visual.has-fitness { + background: none !important; + border: none !important; + border-radius: 0; + aspect-ratio: unset; + padding: 0; + overflow: visible; + box-shadow: none; +} + +.fit-stage { + width: 100%; + max-width: 460px; + margin: 0 auto; +} + +.fit-board { + background: #0d0d0d; + border: 1px solid #1a1a1a; + border-radius: 10px; + overflow: hidden; + box-shadow: 0 20px 60px rgba(0,0,0,0.6); + color: #fff; +} + +.fit-board__header { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 18px; + background: #111; + border-bottom: 1px solid rgba(255,255,255,0.07); +} + +.fit-board__logo { + width: 26px; + height: 26px; + border-radius: 6px; + background: linear-gradient(135deg, var(--color-primary), #ff6b35); + flex-shrink: 0; +} + +.fit-board__title { + font-size: 14px; + font-weight: 700; + color: #fff; +} + +/* Live now hero */ +.fit-now { + padding: 18px 18px 14px; + background: linear-gradient(135deg, rgba(216,51,2,0.1) 0%, rgba(255,107,53,0.06) 100%); + border-bottom: 1px solid rgba(255,255,255,0.06); +} + +.fit-now__badge { + display: inline-flex; + align-items: center; + gap: 6px; + background: var(--color-primary); + color: #fff; + font-size: 10px; + font-weight: 800; + letter-spacing: 1px; + padding: 3px 10px; + border-radius: 4px; + margin-bottom: 8px; + animation: fit-badge-pulse 2s ease-in-out infinite; +} + +.fit-now__name { + font-size: 22px; + font-weight: 800; + color: #fff; + margin-bottom: 4px; +} + +.fit-now__detail { + font-size: 12px; + color: rgba(255,255,255,0.45); + margin-bottom: 12px; +} + +/* Capacity bar */ +.fit-now__capacity { + display: flex; + align-items: center; + gap: 10px; +} + +.fit-cap__lbl { + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.5px; + color: rgba(255,255,255,0.35); + white-space: nowrap; +} + +.fit-cap__track { + flex: 1; + height: 8px; + background: rgba(255,255,255,0.1); + border-radius: 4px; + overflow: hidden; +} + +.fit-cap__fill { + height: 100%; + width: 80%; + background: linear-gradient(90deg, var(--color-primary), #ff6b35); + border-radius: 4px; + animation: fit-cap-breathe 3s ease-in-out infinite alternate; +} + +.fit-cap__val { + font-size: 11px; + font-weight: 600; + color: rgba(255,255,255,0.6); + white-space: nowrap; + font-family: monospace; +} + +/* Upcoming classes list */ +.fit-upcoming { + display: flex; + flex-direction: column; +} + +.fit-class { + display: grid; + grid-template-columns: 48px 1fr 1fr auto; + align-items: center; + gap: 10px; + padding: 10px 18px; + border-bottom: 1px solid rgba(255,255,255,0.04); + font-size: 12px; +} + +.fit-class:last-child { border-bottom: none; } + +.fit-class__time { + font-size: 12px; + font-weight: 600; + color: rgba(255,255,255,0.5); + font-family: monospace; +} + +.fit-class__name { + font-size: 13px; + font-weight: 500; + color: rgba(255,255,255,0.85); +} + +.fit-class__coach { + font-size: 11px; + color: rgba(255,255,255,0.35); +} + +.fit-class__spaces { + font-size: 11px; + color: rgba(255,255,255,0.4); + text-align: right; + white-space: nowrap; +} + +.fit-class--filling .fit-class__spaces { + color: #fbbf24; + font-weight: 600; +} + +@keyframes fit-badge-pulse { + 0%, 100% { box-shadow: 0 0 0 0 rgba(216,51,2,0.5); } + 50% { box-shadow: 0 0 0 6px rgba(216,51,2,0); } +} + +@keyframes fit-cap-breathe { + from { width: 76%; } + to { width: 83%; } +} + +@media (prefers-reduced-motion: reduce) { + .fit-now__badge { animation: none; } + .fit-cap__fill { animation: none; } +} + +@media (max-width: 640px) { + .fit-now { padding: 14px 14px 12px; } + .fit-now__name { font-size: 18px; } + .fit-class { grid-template-columns: 42px 1fr auto; gap: 8px; padding: 9px 14px; } + .fit-class__coach { display: none; } +} diff --git a/theme/assets/js/solutions-animator.js b/theme/assets/js/solutions-animator.js new file mode 100644 index 0000000..e921920 --- /dev/null +++ b/theme/assets/js/solutions-animator.js @@ -0,0 +1,291 @@ +/** + * Solutions Page Animators + * Handles the two JS-driven animations on the Solutions page: + * 1. Live Data board — ticking KPI values + animated sparkline + * 2. Transit board — live clock, split-flap flip characters, row cycling + * + * Both respect prefers-reduced-motion and pause via IntersectionObserver. + * Mirrors the patterns and conventions of dashboard-animator.js. + */ + +/* ── 1. Live Data KPI Animator ─────────────────────────────────────────── */ +(function () { + 'use strict'; + + if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; + + /* KPI definitions: label, base value, unit, variance range, display format */ + var KPIS = [ + { id: 'ld-orders', base: 1847, range: 120, fmt: function (v) { return v.toLocaleString(); } }, + { id: 'ld-uptime', base: 9997, range: 2, fmt: function (v) { return (v / 100).toFixed(2) + '%'; } }, + { id: 'ld-alerts', base: 3, range: 2, fmt: function (v) { return Math.max(0, v).toString(); } }, + { id: 'ld-latency', base: 42, range: 18, fmt: function (v) { return Math.max(8, v) + 'ms'; } }, + ]; + + /* Sparkline path parameters */ + var LINE_PTS = 16; + var LINE_W = 260; + var LINE_H = 60; + var SPEED = 0.0008; + + function wave(t, off) { + return Math.max(0, Math.min(1, + 0.5 + + Math.sin(t + off) * 0.28 + + Math.sin(t * 2.1 + off * 1.7) * 0.12 + )); + } + + function makeState(stage) { + var kpiEls = []; + for (var i = 0; i < KPIS.length; i++) { + kpiEls.push(stage.querySelector('#' + KPIS[i].id)); + } + return { + stage: stage, + kpiEls: kpiEls, + linePath: stage.querySelector('#ld-line-path'), + fillPath: stage.querySelector('#ld-fill-path'), + phase: Math.random() * Math.PI * 2, + ticker: 0, /* frame counter — update KPI text every N frames */ + paused: false, + }; + } + + function updateKpis(st) { + for (var i = 0; i < KPIS.length; i++) { + var el = st.kpiEls[i]; + if (!el) continue; + var k = KPIS[i]; + var raw = Math.round(k.base + wave(st.phase, i * 1.5) * k.range - k.range / 2); + el.textContent = k.fmt(raw); + } + } + + function updateSparkline(st) { + if (!st.linePath) return; + var pts = []; + for (var i = 0; i < LINE_PTS; i++) { + var x = (i / (LINE_PTS - 1)) * LINE_W; + var y = 8 + (1 - wave(st.phase * 0.7, i * 0.8)) * (LINE_H - 16); + pts.push(x.toFixed(1) + ',' + y.toFixed(1)); + } + var d = 'M' + pts.join(' L'); + st.linePath.setAttribute('d', d); + if (st.fillPath) { + st.fillPath.setAttribute('d', d + ' L' + LINE_W + ',' + LINE_H + ' L0,' + LINE_H + ' Z'); + } + } + + function tick(st) { + if (!st.paused) { + st.phase += SPEED * 16; + st.ticker++; + /* Update KPI text every 12 frames (~5/sec at 60fps) for legibility */ + if (st.ticker >= 12) { + st.ticker = 0; + updateKpis(st); + } + updateSparkline(st); + } + requestAnimationFrame(function () { tick(st); }); + } + + function observe(st) { + if (!('IntersectionObserver' in window)) return; + new IntersectionObserver(function (entries) { + for (var i = 0; i < entries.length; i++) { + st.paused = !entries[i].isIntersecting; + } + }, { rootMargin: '200px', threshold: 0.05 }).observe(st.stage); + } + + function boot() { + var stages = document.querySelectorAll('.ld-stage'); + if (!stages.length) return; + for (var i = 0; i < stages.length; i++) { + if (stages[i]._ldAnim) continue; + var st = makeState(stages[i]); + stages[i]._ldAnim = st; + observe(st); + tick(st); + } + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', boot); + } else { + boot(); + } +})(); + +/* ── 2. Transit Departure Board Animator ───────────────────────────────── */ +(function () { + 'use strict'; + + if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) { + /* Still run the clock in reduced-motion mode */ + startClocks(); + return; + } + + /* Departure data sets — cycle between these every CYCLE_MS */ + var CYCLE_MS = 8000; + + var DATA_SETS = [ + [ + { time: '10:14', dest: 'London Victoria', plat: '2', status: 'On Time', cls: 'on-time' }, + { time: '10:22', dest: 'Brighton', plat: '4', status: 'On Time', cls: 'on-time' }, + { time: '10:31', dest: 'Gatwick Airport', plat: '1', status: 'Delayed', cls: 'delayed' }, + { time: '10:45', dest: 'London Bridge', plat: '3', status: 'On Time', cls: 'on-time' }, + { time: '11:02', dest: 'East Croydon', plat: '2', status: 'On Time', cls: 'on-time' }, + ], + [ + { time: '10:22', dest: 'Brighton', plat: '4', status: 'On Time', cls: 'on-time' }, + { time: '10:31', dest: 'Gatwick Airport', plat: '1', status: 'Delayed', cls: 'delayed' }, + { time: '10:45', dest: 'London Bridge', plat: '3', status: 'On Time', cls: 'on-time' }, + { time: '11:02', dest: 'East Croydon', plat: '2', status: 'On Time', cls: 'on-time' }, + { time: '11:14', dest: 'London Victoria', plat: '2', status: 'On Time', cls: 'on-time' }, + ], + [ + { time: '10:31', dest: 'Gatwick Airport', plat: '1', status: 'Delayed', cls: 'delayed' }, + { time: '10:45', dest: 'London Bridge', plat: '3', status: 'On Time', cls: 'on-time' }, + { time: '11:02', dest: 'East Croydon', plat: '2', status: 'On Time', cls: 'on-time' }, + { time: '11:14', dest: 'London Victoria', plat: '2', status: 'On Time', cls: 'on-time' }, + { time: '11:28', dest: 'Three Bridges', plat: '4', status: 'Cancelled', cls: 'cancelled'}, + ], + ]; + + /* ── Clock ── */ + function startClocks() { + var clocks = document.querySelectorAll('#transit-clock'); + if (!clocks.length) return; + + function updateClock() { + var now = new Date(); + var hh = String(now.getHours()).padStart(2, '0'); + var mm = String(now.getMinutes()).padStart(2, '0'); + var ss = String(now.getSeconds()).padStart(2, '0'); + var str = hh + ':' + mm + ':' + ss; + for (var i = 0; i < clocks.length; i++) clocks[i].textContent = str; + } + updateClock(); + setInterval(updateClock, 1000); + } + + /* ── Flip helpers ── */ + function flipCells(rowEl, newDest) { + var flapEls = rowEl.querySelectorAll('.transit-flap'); + var chars = newDest.split(''); + + /* Extend or shrink the flap container to match new length */ + var destCell = rowEl.querySelector('.transit-cell--dest'); + if (!destCell) return; + + /* Animate existing flaps, create/remove extras */ + var i; + for (i = 0; i < chars.length; i++) { + var ch = chars[i] === ' ' ? '\u00a0' : chars[i]; + if (i < flapEls.length) { + /* Animate existing */ + (function (el, character) { + el.classList.add('is-flipping'); + setTimeout(function () { + el.textContent = character; + el.classList.remove('is-flipping'); + }, 125); + })(flapEls[i], ch); + } else { + /* Append new flap */ + var newFlap = document.createElement('span'); + newFlap.className = 'transit-flap is-flipping'; + newFlap.textContent = ch; + destCell.appendChild(newFlap); + setTimeout(function (el) { + el.classList.remove('is-flipping'); + }, 125, newFlap); + } + } + /* Remove surplus flaps */ + for (i = chars.length; i < flapEls.length; i++) { + (function (el) { + el.classList.add('is-flipping'); + setTimeout(function () { el.parentNode && el.parentNode.removeChild(el); }, 250); + })(flapEls[i]); + } + } + + function applyRow(rowEl, departure) { + var timeEl = rowEl.querySelector('.transit-cell--time'); + var platEl = rowEl.querySelector('.transit-cell--plat'); + var statusEl = rowEl.querySelector('.transit-cell--status'); + + if (timeEl) timeEl.textContent = departure.time; + if (platEl) platEl.textContent = departure.platform || departure.plat; + if (statusEl) { + statusEl.textContent = departure.status; + statusEl.className = 'transit-cell transit-cell--status transit-status--' + departure.cls; + } + flipCells(rowEl, departure.dest); + } + + function cycleBoard(stage, dataIdx) { + var rows = stage.querySelectorAll('.transit-row'); + var set = DATA_SETS[dataIdx % DATA_SETS.length]; + + for (var i = 0; i < Math.min(rows.length, set.length); i++) { + /* Stagger each row by 180ms */ + (function (row, dep) { + setTimeout(function () { applyRow(row, dep); }, i * 180); + })(rows[i], set[i]); + } + } + + function initBoard(stage) { + var state = { idx: 0, timer: null, paused: false }; + + function advance() { + if (state.paused) return; + state.idx++; + cycleBoard(stage, state.idx); + } + + function startTimer() { + if (state.timer) return; + state.timer = setInterval(advance, CYCLE_MS); + } + + function stopTimer() { + clearInterval(state.timer); + state.timer = null; + } + + if ('IntersectionObserver' in window) { + new IntersectionObserver(function (entries) { + entries.forEach(function (e) { + state.paused = !e.isIntersecting; + e.isIntersecting ? startTimer() : stopTimer(); + }); + }, { rootMargin: '200px', threshold: 0.05 }).observe(stage); + } + + startTimer(); + } + + function boot() { + startClocks(); + var stages = document.querySelectorAll('.transit-stage'); + if (!stages.length) return; + for (var i = 0; i < stages.length; i++) { + if (stages[i]._transitAnim) continue; + stages[i]._transitAnim = true; + initBoard(stages[i]); + } + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', boot); + } else { + boot(); + } +})(); diff --git a/theme/blocks/index.php b/theme/blocks/index.php index 72e11d4..8756e24 100644 --- a/theme/blocks/index.php +++ b/theme/blocks/index.php @@ -576,6 +576,15 @@ add_action('init', function () { 'cameraAnim' => ['type' => 'boolean', 'default' => false], 'neverGoesDark' => ['type' => 'boolean', 'default' => false], 'brandedAnim' => ['type' => 'boolean', 'default' => false], + 'hospitalityAnim' => ['type' => 'boolean', 'default' => false], + 'retailAnim' => ['type' => 'boolean', 'default' => false], + 'corporateAnim' => ['type' => 'boolean', 'default' => false], + 'educationAnim' => ['type' => 'boolean', 'default' => false], + 'outdoorAnim' => ['type' => 'boolean', 'default' => false], + 'liveDataAnim' => ['type' => 'boolean', 'default' => false], + 'healthcareAnim' => ['type' => 'boolean', 'default' => false], + 'transitAnim' => ['type' => 'boolean', 'default' => false], + 'fitnessAnim' => ['type' => 'boolean', 'default' => false], 'galleryIds' => ['type' => 'array', 'default' => [], 'items' => ['type' => 'number']], ], 'supports' => $block_supports, @@ -2062,6 +2071,355 @@ function oribi_render_platform_row($a) $visual_html = $bd; $visual_cls = 'platform-visual has-branded'; } + elseif (!empty($a['retailAnim'])) { + /* ── Retail Sign: TV cycling 3 promo slides ── */ + $ra = ''; // retail-stage + + $visual_html = $ra; + $visual_cls = 'platform-visual has-retail'; + } + elseif (!empty($a['corporateAnim'])) { + /* ── Corporate: Meeting room door panel ── */ + $ca = ''; // corp-stage + + $visual_html = $ca; + $visual_cls = 'platform-visual has-corporate'; + } + elseif (!empty($a['educationAnim'])) { + /* ── Education: Campus schedule board ── */ + $ea = ''; // edu-stage + + $visual_html = $ea; + $visual_cls = 'platform-visual has-education'; + } + elseif (!empty($a['outdoorAnim'])) { + /* ── Outdoor Marketplace: wide board cycling 2 slides ── */ + $oa = ''; // outdoor-stage + + $visual_html = $oa; + $visual_cls = 'platform-visual has-outdoor'; + } + elseif (!empty($a['liveDataAnim'])) { + /* ── Live Data: Operations centre KPI board ── */ + $la = ''; // ld-stage + + $visual_html = $la; + $visual_cls = 'platform-visual has-live-data'; + } + elseif (!empty($a['healthcareAnim'])) { + /* ── Healthcare: Queue management display ── */ + $hca = ''; // hc-stage + + $visual_html = $hca; + $visual_cls = 'platform-visual has-healthcare'; + } + elseif (!empty($a['transitAnim'])) { + /* ── Transit: Split-flap departure board ── */ + $ta = ''; // transit-stage + + $visual_html = $ta; + $visual_cls = 'platform-visual has-transit'; + } + elseif (!empty($a['fitnessAnim'])) { + /* ── Fitness: Class schedule with live capacity bar ── */ + $fa = ''; // fit-stage + + $visual_html = $fa; + $visual_cls = 'platform-visual has-fitness'; + } elseif (!empty($a['hospitalityAnim'])) { /* ── Hospitality Sign: Rotating Menu (Breakfast, Lunch, Dinner) ── */ $ha = '