Add demo animation support and enhance visual rendering in blocks

- Introduced `demoAnim` property in editor and PHP block definitions.
- Updated UI to include a new toggle for Command Center demo animation.
- Enhanced visual rendering logic to accommodate demo animation alongside existing animations.
- Added HTML structure for Command Center demo animation in the rendering function.
- Implemented additional scenes for partner management, co-marketing, training, and API integration.
This commit is contained in:
Matt Batchelder
2026-04-05 20:26:46 -04:00
parent 4e833fd836
commit f150e67e74
5 changed files with 1791 additions and 30 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -607,6 +607,7 @@
reversed: { type: 'boolean', default: false },
cloudAnim: { type: 'boolean', default: false },
bundleAnim: { type: 'boolean', default: false },
demoAnim: { type: 'boolean', default: false },
imgId: { type: 'number', default: 0 },
imgUrl: { type: 'string', default: '' },
imgAlt: { type: 'string', default: '' },
@@ -649,7 +650,8 @@
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(TG, { label: 'Player + Display Bundle Animation', checked: !!a.bundleAnim, onChange: function (v) { s({ bundleAnim: v }); } })
el(TG, { label: 'Player + Display Bundle Animation', checked: !!a.bundleAnim, onChange: function (v) { s({ bundleAnim: v }); } }),
el(TG, { label: 'Command Center Demo Animation', checked: !!a.demoAnim, onChange: function (v) { s({ demoAnim: v }); } })
),
el(PB, { title: 'Visual Image', initialOpen: false },
el(MUC, null,
@@ -683,7 +685,7 @@
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.bundleAnim ? ' has-pkg-anim' : (a.imgUrl ? ' has-img' : ''))), style: a.reversed ? { direction: 'ltr' } : {} }, visualContent)
el('div', { className: 'about-intro-visual' + (a.cloudAnim ? ' has-cloud-anim' : (a.bundleAnim ? ' has-pkg-anim' : (a.demoAnim ? ' has-demo-anim' : (a.imgUrl ? ' has-img' : '')))), style: a.reversed ? { direction: 'ltr' } : {} }, visualContent)
)
)
)

View File

@@ -341,6 +341,7 @@ add_action('init', function () {
'reversed' => ['type' => 'boolean', 'default' => false],
'cloudAnim' => ['type' => 'boolean', 'default' => false],
'bundleAnim' => ['type' => 'boolean', 'default' => false],
'demoAnim' => ['type' => 'boolean', 'default' => false],
'imgId' => ['type' => 'number', 'default' => 0],
'imgUrl' => ['type' => 'string', 'default' => ''],
'imgAlt' => ['type' => 'string', 'default' => ''],
@@ -408,6 +409,7 @@ add_action('init', function () {
'faIcon' => ['type' => 'string', 'default' => ''],
'title' => ['type' => 'string', 'default' => ''],
'description' => ['type' => 'string', 'default' => ''],
'scene' => ['type' => 'string', 'default' => ''],
],
oribi_card_image_attributes()
),
@@ -610,6 +612,9 @@ add_action('init', function () {
'videoMotionAnim' => ['type' => 'boolean', 'default' => false],
'brandLayoutAnim' => ['type' => 'boolean', 'default' => false],
'menuBoardAnim' => ['type' => 'boolean', 'default' => false],
'whitelabelAnim' => ['type' => 'boolean', 'default' => false],
'resellerAnim' => ['type' => 'boolean', 'default' => false],
'apiAnim' => ['type' => 'boolean', 'default' => false],
'galleryIds' => ['type' => 'array', 'default' => [], 'items' => ['type' => 'number']],
],
'supports' => $block_supports,
@@ -1007,7 +1012,7 @@ function oribi_render_intro_section($a)
<h2 style="margin-bottom:1.5rem;"><?php echo wp_kses_post($a['heading']); ?></h2>
<p class="lead"><?php echo wp_kses_post($a['description']); ?></p>
</div>
<div class="about-intro-visual<?php if (!empty($a['cloudAnim'])) echo ' has-cloud-anim'; elseif (!empty($a['bundleAnim'])) echo ' has-pkg-anim'; ?>"<?php echo $ltr; ?>>
<div class="about-intro-visual<?php if (!empty($a['cloudAnim'])) echo ' has-cloud-anim'; elseif (!empty($a['bundleAnim'])) echo ' has-pkg-anim'; elseif (!empty($a['demoAnim'])) echo ' has-demo-anim'; ?>"<?php echo $ltr; ?>>
<?php
if (!empty($a['cloudAnim'])) {
echo '<div class="ds-anim-container">';
@@ -1033,6 +1038,61 @@ function oribi_render_intro_section($a)
$pkg .= '</div>'; // .pkg-stage
echo $pkg;
}
elseif (!empty($a['demoAnim'])) {
// Command Center demo animation
$cc = '<div class="cc-stage" aria-hidden="true">';
$cc .= '<div class="cc-monitor">';
$cc .= '<div class="cc-monitor__screen">';
$cc .= '<div class="cc-app">';
// Sidebar
$cc .= '<div class="cc-app__nav">';
$cc .= '<div class="cc-nav__logo"></div>';
$cc .= '<div class="cc-nav__item cc-nav__item--active"></div>';
$cc .= '<div class="cc-nav__item"></div>';
$cc .= '<div class="cc-nav__item"></div>';
$cc .= '<div class="cc-nav__item"></div>';
$cc .= '<div class="cc-nav__item"></div>';
$cc .= '</div>';
// Main panel
$cc .= '<div class="cc-app__panel">';
// Top bar
$cc .= '<div class="cc-app__bar">';
$cc .= '<div class="cc-bar__title"></div>';
$cc .= '<div class="cc-bar__live"><div class="cc-live__dot"></div><div class="cc-live__label"></div></div>';
$cc .= '</div>';
// Schedule grid
$cc .= '<div class="cc-schedule">';
// Row 1
$cc .= '<div class="cc-sched__row">';
$cc .= '<div class="cc-sched__label"></div>';
$cc .= '<div class="cc-sched__track"><div class="cc-block cc-block--a"></div><div class="cc-block cc-block--gap"></div><div class="cc-block cc-block--b"></div></div>';
$cc .= '</div>';
// Row 2
$cc .= '<div class="cc-sched__row">';
$cc .= '<div class="cc-sched__label"></div>';
$cc .= '<div class="cc-sched__track"><div class="cc-block cc-block--c"></div><div class="cc-block cc-block--a cc-block--sm"></div></div>';
$cc .= '</div>';
// Row 3
$cc .= '<div class="cc-sched__row">';
$cc .= '<div class="cc-sched__label"></div>';
$cc .= '<div class="cc-sched__track"><div class="cc-block cc-block--b cc-block--sm"></div><div class="cc-block cc-block--d"></div></div>';
$cc .= '</div>';
$cc .= '</div>'; // .cc-schedule
// Content library row
$cc .= '<div class="cc-library">';
$cc .= '<div class="cc-lib__item"></div>';
$cc .= '<div class="cc-lib__item"></div>';
$cc .= '<div class="cc-lib__item"></div>';
$cc .= '<div class="cc-lib__item"></div>';
$cc .= '</div>';
$cc .= '</div>'; // .cc-app__panel
$cc .= '</div>'; // .cc-app
$cc .= '</div>'; // .cc-monitor__screen
$cc .= '<div class="cc-monitor__stand"><div class="cc-monitor__stem"></div><div class="cc-monitor__base"></div></div>';
$cc .= '</div>'; // .cc-monitor
$cc .= '</div>'; // .cc-stage
echo $cc;
}
else {
echo wp_kses_post($a['visual']);
}
@@ -1231,6 +1291,163 @@ function oribi_render_card_scene($scene)
. '</div>'
. '</div></div>';
/* ── Partners page scenes ──────────────────────────────────────────── */
case 'margins':
return
'<div class="card-scene cs-margins" aria-hidden="true">'
. '<div class="cs-screen">'
. '<div class="cs-topbar"><div class="cs-dots"><span></span><span></span><span></span></div><div class="cs-title">Partner Tiers</div></div>'
. '<div class="cs-tiers">'
. '<div class="cs-tier-row">'
. '<span class="cs-tier-name">Silver</span>'
. '<div class="cs-tier-track"><div class="cs-tier-fill cs-tf--silver"></div></div>'
. '<span class="cs-tier-pct">25%</span>'
. '</div>'
. '<div class="cs-tier-row cs-tier-row--active">'
. '<span class="cs-tier-name">Gold &#9733;</span>'
. '<div class="cs-tier-track"><div class="cs-tier-fill cs-tf--gold"></div></div>'
. '<span class="cs-tier-pct">32%</span>'
. '</div>'
. '<div class="cs-tier-row">'
. '<span class="cs-tier-name">Platinum</span>'
. '<div class="cs-tier-track"><div class="cs-tier-fill cs-tf--plat"></div></div>'
. '<span class="cs-tier-pct">40%</span>'
. '</div>'
. '</div>'
. '<div class="cs-tier-note">Volume-based margin model</div>'
. '</div></div>';
case 'partner-mgr':
return
'<div class="card-scene cs-partner-mgr" aria-hidden="true">'
. '<div class="cs-screen">'
. '<div class="cs-topbar"><div class="cs-live-dot"></div><div class="cs-title">Account Manager</div></div>'
. '<div class="cs-mgr">'
. '<div class="cs-mgr__avatar">JK</div>'
. '<div class="cs-mgr__info"><div class="cs-mgr__name">Jamie K.</div><div class="cs-mgr__role">Partner Success</div></div>'
. '</div>'
. '<div class="cs-mgr__rows">'
. '<div class="cs-mgr__row"><span class="cs-mgr__icon cs-mgr__icon--phone"></span><span class="cs-mgr__val">Direct line available</span></div>'
. '<div class="cs-mgr__row"><span class="cs-mgr__icon cs-mgr__icon--reply"></span><span class="cs-mgr__val">Avg reply &lt; 2 hrs</span></div>'
. '</div>'
. '<div class="cs-mgr__status"><span class="cs-dot cs-dot--green"></span>Online now</div>'
. '</div></div>';
case 'co-marketing':
return
'<div class="card-scene cs-co-marketing" aria-hidden="true">'
. '<div class="cs-screen">'
. '<div class="cs-topbar"><div class="cs-dots"><span></span><span></span><span></span></div><div class="cs-title">Campaigns</div></div>'
. '<div class="cs-campaigns">'
. '<div class="cs-camp"><span class="cs-camp__ch">Case Study</span><span class="cs-camp__badge cs-badge--live">Live</span></div>'
. '<div class="cs-camp"><span class="cs-camp__ch">LinkedIn Post</span><span class="cs-camp__badge cs-badge--sched">Scheduled</span></div>'
. '<div class="cs-camp"><span class="cs-camp__ch">Email Campaign</span><span class="cs-camp__badge cs-badge--draft">Draft</span></div>'
. '</div>'
. '</div></div>';
case 'training':
return
'<div class="card-scene cs-training" aria-hidden="true">'
. '<div class="cs-screen">'
. '<div class="cs-topbar"><div class="cs-dots"><span></span><span></span><span></span></div><div class="cs-title">Training Portal</div></div>'
. '<div class="cs-courses">'
. '<div class="cs-course">'
. '<span class="cs-course__name">Platform Fundamentals</span>'
. '<div class="cs-course__bar"><div class="cs-course__fill cs-fill--100"></div></div>'
. '<span class="cs-course__done">&#10003;</span>'
. '</div>'
. '<div class="cs-course">'
. '<span class="cs-course__name">Sales Playbook</span>'
. '<div class="cs-course__bar"><div class="cs-course__fill cs-fill--anim"></div></div>'
. '<span class="cs-course__pct">78%</span>'
. '</div>'
. '<div class="cs-course">'
. '<span class="cs-course__name">API Integration</span>'
. '<div class="cs-course__bar"><div class="cs-course__fill cs-fill--45"></div></div>'
. '<span class="cs-course__pct">45%</span>'
. '</div>'
. '</div>'
. '</div></div>';
case 'priority-support':
return
'<div class="card-scene cs-priority-support" aria-hidden="true">'
. '<div class="cs-screen">'
. '<div class="cs-topbar"><div class="cs-live-dot"></div><div class="cs-title">Support</div></div>'
. '<div class="cs-ticket">'
. '<div class="cs-ticket__hdr"><span class="cs-ticket__id">#P-4821</span><span class="cs-ticket__pri">&#9650; Priority</span></div>'
. '<div class="cs-ticket__title">API sync not updating screens</div>'
. '<div class="cs-ticket__meta">'
. '<span class="cs-ticket__sla">SLA: <strong class="cs-sla-timer">4m 32s</strong></span>'
. '<span class="cs-ticket__agent">Sarah M.</span>'
. '</div>'
. '<div class="cs-ticket__status"><span class="cs-dot cs-dot--orange"></span>In Progress</div>'
. '</div>'
. '</div></div>';
case 'early-access':
return
'<div class="card-scene cs-early-access" aria-hidden="true">'
. '<div class="cs-screen">'
. '<div class="cs-topbar"><div class="cs-dots"><span></span><span></span><span></span></div><div class="cs-title">Roadmap Preview</div></div>'
. '<div class="cs-features">'
. '<div class="cs-feat cs-feat--1"><span class="cs-feat__name">AI Content Generator</span><span class="cs-feat__tag cs-feat__tag--beta">Beta</span></div>'
. '<div class="cs-feat cs-feat--2"><span class="cs-feat__name">Analytics v2</span><span class="cs-feat__tag cs-feat__tag--early">Early Access</span></div>'
. '<div class="cs-feat cs-feat--3"><span class="cs-feat__name">Multi-tenant SSO</span><span class="cs-feat__tag cs-feat__tag--q3">Q3 2026</span></div>'
. '</div>'
. '</div></div>';
case 'av-network':
return
'<div class="card-scene cs-av-network" aria-hidden="true">'
. '<div class="cs-screen">'
. '<div class="cs-topbar"><div class="cs-dots"><span></span><span></span><span></span></div><div class="cs-title">AV Installation</div></div>'
. '<div class="cs-avnet">'
. '<svg class="cs-avnet__svg" viewBox="0 0 200 90" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">'
. '<line x1="100" y1="45" x2="30" y2="18" stroke="#D83302" stroke-width="1.2" stroke-dasharray="3 2" opacity=".7"/>'
. '<line x1="100" y1="45" x2="100" y2="10" stroke="#D83302" stroke-width="1.2" stroke-dasharray="3 2" opacity=".7"/>'
. '<line x1="100" y1="45" x2="170" y2="18" stroke="#D83302" stroke-width="1.2" stroke-dasharray="3 2" opacity=".7"/>'
. '<line x1="100" y1="45" x2="30" y2="75" stroke="#4CAF50" stroke-width="1.2" stroke-dasharray="3 2" opacity=".7"/>'
. '<line x1="100" y1="45" x2="170" y2="75" stroke="#4CAF50" stroke-width="1.2" stroke-dasharray="3 2" opacity=".7"/>'
. '<rect class="cs-av-hub" x="88" y="33" width="24" height="24" rx="6" fill="#D83302"/>'
. '<rect class="cs-av-scr cs-av-scr--1" x="18" y="8" width="24" height="18" rx="3" fill="#1a2236" stroke="#D83302" stroke-width="1"/>'
. '<rect class="cs-av-scr cs-av-scr--2" x="88" y="1" width="24" height="18" rx="3" fill="#1a2236" stroke="#D83302" stroke-width="1"/>'
. '<rect class="cs-av-scr cs-av-scr--3" x="158" y="8" width="24" height="18" rx="3" fill="#1a2236" stroke="#D83302" stroke-width="1"/>'
. '<rect class="cs-av-scr cs-av-scr--4" x="18" y="65" width="24" height="18" rx="3" fill="#1a2236" stroke="#4CAF50" stroke-width="1"/>'
. '<rect class="cs-av-scr cs-av-scr--5" x="158" y="65" width="24" height="18" rx="3" fill="#1a2236" stroke="#4CAF50" stroke-width="1"/>'
. '</svg>'
. '</div>'
. '<div class="cs-avnet__label">5 screens managed</div>'
. '</div></div>';
case 'it-managed':
return
'<div class="card-scene cs-it-managed" aria-hidden="true">'
. '<div class="cs-screen">'
. '<div class="cs-topbar"><div class="cs-live-dot"></div><div class="cs-title">Managed Screens</div></div>'
. '<div class="cs-devices">'
. '<div class="cs-device"><span class="cs-dot cs-dot--green"></span><span class="cs-device__name">Lobby Display</span><span class="cs-device__uptime">99.8%</span></div>'
. '<div class="cs-device"><span class="cs-dot cs-dot--green"></span><span class="cs-device__name">Floor 3 Conf.</span><span class="cs-device__uptime">99.5%</span></div>'
. '<div class="cs-device"><span class="cs-dot cs-dot--orange"></span><span class="cs-device__name">Café Display</span><span class="cs-device__uptime">Reboot</span></div>'
. '<div class="cs-device"><span class="cs-dot cs-dot--green"></span><span class="cs-device__name">Reception</span><span class="cs-device__uptime">100%</span></div>'
. '</div>'
. '</div></div>';
case 'api-embed':
return
'<div class="card-scene cs-api-embed" aria-hidden="true">'
. '<div class="cs-screen">'
. '<div class="cs-topbar"><div class="cs-dots"><span></span><span></span><span></span></div><div class="cs-title">SDK Integration</div></div>'
. '<div class="cs-code">'
. '<div class="cs-code__line"><span class="cs-kw">import</span> <span class="cs-cl">OTSSigns</span> <span class="cs-kw">from</span> <span class="cs-str">\'ots-signs-sdk\'</span><span class="cs-code__cursor"></span></div>'
. '<div class="cs-code__line cs-code__line--2"><span class="cs-kw">const</span> <span class="cs-var">client</span> <span class="cs-op">=</span> <span class="cs-kw">new</span> <span class="cs-cl">OTSSigns</span><span class="cs-pu">(</span></div>'
. '<div class="cs-code__line cs-code__line--3"><span class="cs-indent"> apiKey<span class="cs-op">:</span> <span class="cs-str">process.env.OTS_KEY</span></span></div>'
. '<div class="cs-code__line cs-code__line--4"><span class="cs-pu">)</span></div>'
. '<div class="cs-code__line cs-code__line--5"><span class="cs-kw">await</span> <span class="cs-var">client</span><span class="cs-pu">.</span><span class="cs-fn">publish</span><span class="cs-pu">({</span> <span class="cs-var">screen</span><span class="cs-op">:</span> <span class="cs-str">&#39;lobby&#39;</span> <span class="cs-pu">})</span></div>'
. '</div>'
. '</div></div>';
default:
return '';
}
@@ -1247,6 +1464,7 @@ function oribi_render_value_card($a)
{
$img = oribi_card_image_html($a);
$img_cls = $img['card_class'] ? ' ' . $img['card_class'] : '';
$scene = !empty($a['scene']) ? trim($a['scene']) : '';
ob_start();
if ($img['html'] && $img['position'] === 'background'): ?>
@@ -1273,7 +1491,9 @@ function oribi_render_value_card($a)
<div class="oribi-card value-card<?php echo esc_attr($img_cls); ?>">
<?php if ($img['html'] && ($img['position'] === 'top' || $img['position'] === 'replace-icon'))
echo $img['html']; ?>
<?php if ((!$img['html'] || $img['position'] !== 'replace-icon') && oribi_has_icon($a)): ?>
<?php if ($scene): ?>
<?php echo oribi_render_card_scene($scene); ?>
<?php elseif ((!$img['html'] || $img['position'] !== 'replace-icon') && oribi_has_icon($a)): ?>
<div class="value-icon"><?php echo oribi_render_icon($a); ?></div>
<?php
endif; ?>
@@ -3267,15 +3487,144 @@ function oribi_render_platform_row($a)
}
}
elseif (!empty($a['whitelabelAnim'])) {
/* ── White-Label Platform: branding customiser UI ── */
$wl = '<div class="wl-stage" aria-hidden="true">';
/* Left: live preview pane */
$wl .= '<div class="wl-preview">';
$wl .= '<div class="wl-preview__bar">';
$wl .= '<div class="wl-preview__logo"></div>';
$wl .= '<div class="wl-preview__nav"><span></span><span></span><span></span></div>';
$wl .= '</div>';
$wl .= '<div class="wl-preview__body">';
$wl .= '<div class="wl-preview__side"><span></span><span></span><span></span><span></span></div>';
$wl .= '<div class="wl-preview__main">';
$wl .= '<div class="wl-preview__screens">';
$wl .= '<div class="wl-preview__screen wl-ps--1"></div>';
$wl .= '<div class="wl-preview__screen wl-ps--2"></div>';
$wl .= '<div class="wl-preview__screen wl-ps--3"></div>';
$wl .= '<div class="wl-preview__screen wl-ps--4"></div>';
$wl .= '<div class="wl-preview__screen wl-ps--5"></div>';
$wl .= '<div class="wl-preview__screen wl-ps--6"></div>';
$wl .= '</div>';
$wl .= '</div>';
$wl .= '</div>';
$wl .= '</div>';
/* Right: brand settings panel */
$wl .= '<div class="wl-panel">';
$wl .= '<div class="wl-panel__title">Brand Settings</div>';
$wl .= '<div class="wl-field">';
$wl .= '<span class="wl-field__label">Logo</span>';
$wl .= '<div class="wl-logo-slot"><span class="wl-logo-slot__text">YOUR LOGO</span></div>';
$wl .= '</div>';
$wl .= '<div class="wl-field">';
$wl .= '<span class="wl-field__label">Primary Colour</span>';
$wl .= '<div class="wl-swatches">';
$wl .= '<div class="wl-swatch wl-swatch--1"></div>';
$wl .= '<div class="wl-swatch wl-swatch--2"></div>';
$wl .= '<div class="wl-swatch wl-swatch--3 wl-swatch--active"></div>';
$wl .= '<div class="wl-swatch wl-swatch--4"></div>';
$wl .= '</div>';
$wl .= '</div>';
$wl .= '<div class="wl-field">';
$wl .= '<span class="wl-field__label">Custom Domain</span>';
$wl .= '<div class="wl-domain">signs.yourbrand.com</div>';
$wl .= '</div>';
$wl .= '<div class="wl-status"><span class="wl-status__dot"></span>Brand applied &amp; live</div>';
$wl .= '</div>'; /* wl-panel */
$wl .= '</div>'; /* wl-stage */
$visual_html = $wl;
$visual_cls = 'platform-visual has-whitelabel';
}
elseif (!empty($a['resellerAnim'])) {
/* ── Reseller Programme: partner portal with tier dashboard ── */
$rs = '<div class="rsl-stage" aria-hidden="true">';
$rs .= '<div class="rsl-panel">';
/* Header */
$rs .= '<div class="rsl-header">';
$rs .= '<div class="rsl-header__left">';
$rs .= '<div class="rsl-badge">&#9733; Gold Partner</div>';
$rs .= '<div class="rsl-sub">Member since 2023</div>';
$rs .= '</div>';
$rs .= '<div class="rsl-screens"><span class="rsl-screens__val">248</span><span class="rsl-screens__lbl">screens</span></div>';
$rs .= '</div>';
/* KPI row */
$rs .= '<div class="rsl-kpis">';
$rs .= '<div class="rsl-kpi"><div class="rsl-kpi__val">32%</div><div class="rsl-kpi__lbl">Margin</div></div>';
$rs .= '<div class="rsl-kpi rsl-kpi--primary"><div class="rsl-kpi__val">$2,840</div><div class="rsl-kpi__lbl">This month</div></div>';
$rs .= '<div class="rsl-kpi"><div class="rsl-kpi__val">14</div><div class="rsl-kpi__lbl">Clients</div></div>';
$rs .= '</div>';
/* Tier progress bars */
$rs .= '<div class="rsl-tiers">';
$tiers = [
['name' => 'Silver', 'rate' => '25%', 'cls' => 'silver', 'w' => '100'],
['name' => 'Gold &#9733;', 'rate' => '32%', 'cls' => 'gold', 'w' => '68', 'active' => true],
['name' => 'Platinum', 'rate' => '40%', 'cls' => 'plat', 'w' => '30'],
];
foreach ($tiers as $t) {
$act = !empty($t['active']) ? ' rsl-tier--active' : '';
$rs .= '<div class="rsl-tier' . $act . '">';
$rs .= '<span class="rsl-tier__name">' . $t['name'] . '</span>';
$rs .= '<div class="rsl-tier__bar"><div class="rsl-tier__fill rsl-tf--' . $t['cls'] . '" style="width:' . $t['w'] . '%"></div></div>';
$rs .= '<span class="rsl-tier__rate">' . $t['rate'] . '</span>';
$rs .= '</div>';
}
$rs .= '</div>';
/* Progress to Platinum */
$rs .= '<div class="rsl-next">';
$rs .= '<span class="rsl-next__lbl">116 screens to Platinum</span>';
$rs .= '<div class="rsl-next__bar"><div class="rsl-next__fill"></div></div>';
$rs .= '</div>';
$rs .= '</div>'; /* rsl-panel */
$rs .= '</div>'; /* rsl-stage */
$visual_html = $rs;
$visual_cls = 'platform-visual has-reseller';
}
elseif (!empty($a['apiAnim'])) {
/* ── API & Platform Integration: REST API console ── */
$ap = '<div class="api-stage" aria-hidden="true">';
$ap .= '<div class="api-console">';
$ap .= '<div class="api-console__bar">';
$ap .= '<div class="api-dots"><span class="api-dot api-dot--r"></span><span class="api-dot api-dot--y"></span><span class="api-dot api-dot--g"></span></div>';
$ap .= '<span class="api-console__title">API Console</span>';
$ap .= '<span class="api-console__version">v1</span>';
$ap .= '</div>';
$ap .= '<div class="api-console__body">';
$requests = [
['method' => 'POST', 'cls' => 'post', 'endpoint' => '/v1/screens/content', 'status' => '200', 'resp' => '{ "published": true, "screens": 48 }'],
['method' => 'GET', 'cls' => 'get', 'endpoint' => '/v1/screens?group=lobby', 'status' => '200', 'resp' => '{ "total": 12, "online": 12 }'],
['method' => 'PATCH', 'cls' => 'patch', 'endpoint' => '/v1/content/schedule', 'status' => '200', 'resp' => '{ "scheduled": true, "at": "2026-04-06T09:00Z" }'],
];
foreach ($requests as $i => $r) {
$n = $i + 1;
$ap .= '<div class="api-req api-req--' . $n . '">';
$ap .= '<span class="api-method api-method--' . $r['cls'] . '">' . $r['method'] . '</span>';
$ap .= '<span class="api-endpoint">' . $r['endpoint'] . '</span>';
$ap .= '<span class="api-status">200</span>';
$ap .= '</div>';
$ap .= '<div class="api-res api-res--' . $n . '"><span class="api-json">' . $r['resp'] . '</span></div>';
}
$ap .= '<div class="api-cursor"></div>';
$ap .= '</div>'; /* api-console__body */
$ap .= '</div>'; /* api-console */
$ap .= '</div>'; /* api-stage */
$visual_html = $ap;
$visual_cls = 'platform-visual has-api';
}
else {
$visual_html = oribi_render_icon($a['visual'] ?? '');
$visual_cls = 'platform-visual';
}
ob_start(); ?>
<div class="platform-row<?php echo $rev; ?>">
<div class="platform-text">
<h3><?php echo wp_kses_post($a['heading']); ?></h3>
<p><?php echo wp_kses_post($a['description']); ?></p>
<?php if (!empty($a['btnUrl'])): ?>
<a href="<?php echo esc_url($a['btnUrl']); ?>" class="btn btn-outline mt-3"><?php echo esc_html($a['btnText'] ?? 'Learn More'); ?> &rarr;</a>