Files
OTSSigns-Website/theme/blocks/index.php
Matt Batchelder 2edbf9732b Add industry mockup animator script for solutions page
- Enqueued new script `industry-animator.js` for animated device mockups.
- Implemented animation logic for various industries including hospitality, retail, corporate, education, outdoor, and live data displays.
- Utilized IntersectionObserver for performance optimization by pausing animations when off-screen.
2026-02-21 13:59:57 -05:00

2582 lines
152 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* OTS Theme - Custom Gutenberg Blocks (InnerBlocks Architecture)
*
* Blocks:
* Standalone: hero, page-hero, cta-banner, intro-section, contact-section
* Parent/child pairs:
* feature-section → feature-card
* value-section → value-card
* addon-section → addon-card
* image-section → image-card
* stat-section → stat-card
* link-section → link-card
* pricing-section → pricing-card
* platform-section → platform-row
* trust-section → trust-item
* faq-section → faq-item
*
* Parent blocks use InnerBlocks (save → InnerBlocks.Content, PHP wraps $content).
* Child blocks are dynamic (save → null, own render_callback).
*/
if ( ! defined( 'ABSPATH' ) ) exit;
/* ══════════════════════════════════════════════════════════════════════════════
SHARED HELPERS
══════════════════════════════════════════════════════════════════════════════ */
/**
* Shared image attributes for all card blocks.
* Spread into each card's 'attributes' array.
*/
function oribi_card_image_attributes() {
return [
'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' ],
];
}
/**
* Build card image HTML from shared image attributes.
*
* @param array $a Block attributes (must include imgId, imgUrl, etc.)
* @return array [ 'html' => string, 'position' => string, 'card_class' => string ]
*/
function oribi_card_image_html( $a ) {
$img_id = ! empty( $a['imgId'] ) ? intval( $a['imgId'] ) : 0;
$img_url = ! empty( $a['imgUrl'] ) ? $a['imgUrl'] : '';
$img_alt = ! empty( $a['imgAlt'] ) ? $a['imgAlt'] : '';
$img_w = ! empty( $a['imgWidth'] ) ? intval( $a['imgWidth'] ) : 80;
$img_h = ! empty( $a['imgHeight'] ) ? intval( $a['imgHeight'] ) : 0;
$img_fit = ! empty( $a['imgFit'] ) ? $a['imgFit'] : 'contain';
$img_pos = ! empty( $a['imgPosition'] ) ? $a['imgPosition'] : 'top';
$result = [
'html' => '',
'position' => $img_pos,
'card_class' => '',
];
if ( ! $img_url ) {
return $result;
}
$result['card_class'] = 'img-' . $img_pos;
// Build inline style for dimensions
$styles = [];
if ( $img_pos !== 'background' ) {
if ( $img_w > 0 ) $styles[] = 'width:' . $img_w . 'px';
$styles[] = 'max-width:100%';
if ( $img_h > 0 ) {
$styles[] = 'height:' . $img_h . 'px';
} else {
$styles[] = 'height:auto';
}
}
$style_str = implode( ';', $styles );
$fit_class = 'oribi-card-img oribi-card-img--' . ( $img_pos === 'background' ? 'cover' : esc_attr( $img_fit ) );
if ( $img_id ) {
$result['html'] = wp_get_attachment_image( $img_id, 'large', false, [
'class' => $fit_class,
'style' => $style_str,
'alt' => $img_alt,
] );
} else {
$result['html'] = '<img class="' . esc_attr( $fit_class ) . '" src="' . esc_url( $img_url )
. '" alt="' . esc_attr( $img_alt )
. '" style="' . esc_attr( $style_str ) . '">';
}
// Wrap in container
$result['html'] = '<div class="oribi-card-img-wrap">' . $result['html'] . '</div>';
return $result;
}
/**
* Shared icon attributes added to every card block that supports an icon.
*/
function oribi_card_icon_attributes() {
return [
'icon' => [ 'type' => 'string', 'default' => '' ],
'iconType' => [ 'type' => 'string', 'default' => 'emoji' ],
'faIcon' => [ 'type' => 'string', 'default' => '' ],
];
}
/**
* Render an icon from block attributes.
*
* Accepts either:
* - iconType = 'fontawesome' + faIcon = 'fas fa-cloud' → <i class="fas fa-cloud"></i>
* - iconType = 'emoji' + icon = 'text' -> text (escaped)
*
* Legacy: if $icon_or_attrs is a plain string it behaves like before.
*
* @param array|string $icon_or_attrs Block attributes array, or legacy icon string.
* @return string Rendered HTML.
*/
function oribi_render_icon( $icon_or_attrs ) {
// Legacy string call
if ( is_string( $icon_or_attrs ) ) {
$icon = $icon_or_attrs;
if ( empty( $icon ) ) return '';
if ( preg_match( '/^fa[srldb]\s+fa-/', $icon ) ) {
return '<i class="' . esc_attr( $icon ) . '"></i>';
}
return wp_kses_post( $icon );
}
// Array (block attributes)
$a = $icon_or_attrs;
$iconType = ! empty( $a['iconType'] ) ? $a['iconType'] : 'emoji';
if ( $iconType === 'fontawesome' ) {
$fa = ! empty( $a['faIcon'] ) ? trim( $a['faIcon'] ) : '';
if ( empty( $fa ) ) return '';
return '<i class="' . esc_attr( $fa ) . '" aria-hidden="true"></i>';
}
// emoji / text
$icon = ! empty( $a['icon'] ) ? $a['icon'] : '';
if ( empty( $icon ) ) return '';
return wp_kses_post( $icon );
}
/**
* Return true when the given block attributes specify a non-empty icon.
*/
function oribi_has_icon( $a ) {
$iconType = ! empty( $a['iconType'] ) ? $a['iconType'] : 'emoji';
if ( $iconType === 'fontawesome' ) {
return ! empty( $a['faIcon'] );
}
return ! empty( $a['icon'] );
}
/**
* Render a standard card section wrapper.
*
* @param array $a Block attributes (variant, label, heading, lead, columns).
* @param string $content InnerBlocks rendered HTML.
* @param string $grid_class CSS class for the grid container (e.g. 'grid').
* @param int $default_cols Default column count if not set in attributes.
* @return string Rendered HTML.
*/
function oribi_render_card_section( $a, $content, $grid_class = 'grid', $default_cols = 3 ) {
$cls = ( ! empty( $a['variant'] ) && $a['variant'] === 'alt' ) ? 'section section-alt' : 'section';
$cols = ! empty( $a['columns'] ) ? intval( $a['columns'] ) : $default_cols;
$grid = $grid_class . '-' . $cols;
ob_start(); ?>
<section class="<?php echo esc_attr( $cls ); ?>">
<div class="container">
<div class="section-header">
<?php if ( ! empty( $a['label'] ) ) : ?><span class="section-label"><?php echo esc_html( $a['label'] ); ?></span><?php endif; ?>
<h2><?php echo wp_kses_post( $a['heading'] ?? '' ); ?></h2>
<?php if ( ! empty( $a['lead'] ) ) : ?><p class="lead"><?php echo wp_kses_post( $a['lead'] ); ?></p><?php endif; ?>
</div>
<div class="<?php echo esc_attr( $grid ); ?>">
<?php echo $content; ?>
</div>
</div>
</section>
<?php return ob_get_clean();
}
/**
* Standard section attributes shared by all card section wrappers.
*/
function oribi_card_section_attributes( $default_cols = 3 ) {
return [
'variant' => [ 'type' => 'string', 'default' => 'normal' ],
'label' => [ 'type' => 'string', 'default' => '' ],
'heading' => [ 'type' => 'string', 'default' => '' ],
'lead' => [ 'type' => 'string', 'default' => '' ],
'columns' => [ 'type' => 'number', 'default' => $default_cols ],
];
}
/* ── Block category ────────────────────────────────────────────────────────── */
add_filter( 'block_categories_all', function ( $cats ) {
array_unshift( $cats, [ 'slug' => 'oribi', 'title' => 'OTS Theme' ] );
return $cats;
} );
/* ── Enqueue editor assets ─────────────────────────────────────────────────── */
add_action( 'enqueue_block_editor_assets', function () {
$dir = get_template_directory();
$uri = get_template_directory_uri();
wp_enqueue_script(
'oribi-blocks',
$uri . '/blocks/editor.js',
[ 'wp-blocks', 'wp-element', 'wp-block-editor', 'wp-components', 'wp-i18n' ],
filemtime( $dir . '/blocks/editor.js' ),
true
);
wp_enqueue_style(
'oribi-blocks-editor',
$uri . '/blocks/editor.css',
[ 'wp-edit-blocks' ],
filemtime( $dir . '/blocks/editor.css' )
);
} );
/* ── Register all blocks ───────────────────────────────────────────────────── */
add_action( 'init', function () {
/* Shared supports - exposes color pickers and font-size selector in the
block inspector for every Oribi block. Individual blocks can override
these by merging their own array if needed. */
$block_supports = [
'color' => [
'text' => true,
'background' => true,
'link' => true,
],
'typography' => [
'fontSize' => true,
'lineHeight' => true,
],
'spacing' => [
'padding' => true,
'margin' => true,
],
];
/* ── TEMPLATE-PART HELPER BLOCKS ──────────────────────────────────────── */
register_block_type( 'oribi/site-header', [
'render_callback' => 'oribi_render_site_header',
] );
register_block_type( 'oribi/site-footer', [
'render_callback' => 'oribi_render_site_footer',
] );
/* ── STANDALONE BLOCKS ─────────────────────────────────────────────────── */
register_block_type( 'oribi/hero', [
'attributes' => [
'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' ],
],
'supports' => $block_supports,
'render_callback' => 'oribi_render_hero',
] );
register_block_type( 'oribi/page-hero', [
'attributes' => [
'label' => [ 'type' => 'string', 'default' => '' ],
'title' => [ 'type' => 'string', 'default' => '' ],
'description' => [ 'type' => 'string', 'default' => '' ],
],
'supports' => $block_supports,
'render_callback' => 'oribi_render_page_hero',
] );
register_block_type( 'oribi/cta-banner', [
'attributes' => [
'heading' => [ 'type' => 'string', 'default' => '' ],
'text' => [ 'type' => 'string', 'default' => '' ],
'btnText' => [ 'type' => 'string', 'default' => '' ],
'btnUrl' => [ 'type' => 'string', 'default' => '' ],
],
'supports' => $block_supports,
'render_callback' => 'oribi_render_cta_banner',
] );
register_block_type( 'oribi/intro-section', [
'attributes' => [
'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 ],
],
'supports' => $block_supports,
'render_callback' => 'oribi_render_intro_section',
] );
register_block_type( 'oribi/contact-section', [
'attributes' => [
'heading' => [ 'type' => 'string', 'default' => "Let's Talk" ],
'lead' => [ 'type' => 'string', 'default' => '' ],
'email' => [ 'type' => 'string', 'default' => 'solutions@oribi-tech.com' ],
'supportUrl' => [ 'type' => 'string', 'default' => 'https://portal.oribi-tech.com/helpdesk/technical-support-1' ],
'portalUrl' => [ 'type' => 'string', 'default' => 'https://portal.oribi-tech.com/' ],
'location' => [ 'type' => 'string', 'default' => 'Saratoga Springs, Upstate New York' ],
'formHeading' => [ 'type' => 'string', 'default' => 'Want to Learn More?' ],
],
'supports' => $block_supports,
'render_callback' => 'oribi_render_contact_section',
] );
/* ── PARENT / CHILD PAIRS ──────────────────────────────────────────────── */
/* Feature Section (parent) */
register_block_type( 'oribi/feature-section', [
'attributes' => oribi_card_section_attributes( 3 ),
'supports' => $block_supports,
'render_callback' => 'oribi_render_feature_section',
] );
/* Feature Card (child) */
register_block_type( 'oribi/feature-card', [
'attributes' => array_merge(
[
'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 ],
],
oribi_card_image_attributes()
),
'supports' => $block_supports,
'render_callback' => 'oribi_render_feature_card',
] );
/* Value Section (parent) */
register_block_type( 'oribi/value-section', [
'attributes' => oribi_card_section_attributes( 3 ),
'supports' => $block_supports,
'render_callback' => 'oribi_render_value_section',
] );
/* Value Card (child) */
register_block_type( 'oribi/value-card', [
'attributes' => array_merge(
[
'icon' => [ 'type' => 'string', 'default' => '' ],
'iconType' => [ 'type' => 'string', 'default' => 'emoji' ],
'faIcon' => [ 'type' => 'string', 'default' => '' ],
'title' => [ 'type' => 'string', 'default' => '' ],
'description' => [ 'type' => 'string', 'default' => '' ],
],
oribi_card_image_attributes()
),
'supports' => $block_supports,
'render_callback' => 'oribi_render_value_card',
] );
/* Addon Section (parent) */
register_block_type( 'oribi/addon-section', [
'attributes' => oribi_card_section_attributes( 3 ),
'supports' => $block_supports,
'render_callback' => 'oribi_render_addon_section',
] );
/* Addon Card (child) */
register_block_type( 'oribi/addon-card', [
'attributes' => array_merge(
[
'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' => '' ],
],
oribi_card_image_attributes()
),
'supports' => $block_supports,
'render_callback' => 'oribi_render_addon_card',
] );
/* Image Section (parent) */
register_block_type( 'oribi/image-section', [
'attributes' => oribi_card_section_attributes( 3 ),
'supports' => $block_supports,
'render_callback' => 'oribi_render_image_section',
] );
/* Image Card (child) */
register_block_type( 'oribi/image-card', [
'attributes' => array_merge(
[
'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' => '' ],
],
oribi_card_image_attributes()
),
'supports' => $block_supports,
'render_callback' => 'oribi_render_image_card',
] );
/* Stat Section (parent) */
register_block_type( 'oribi/stat-section', [
'attributes' => oribi_card_section_attributes( 3 ),
'supports' => $block_supports,
'render_callback' => 'oribi_render_stat_section',
] );
/* Stat Card (child) */
register_block_type( 'oribi/stat-card', [
'attributes' => array_merge(
[
'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' => '' ],
],
oribi_card_image_attributes()
),
'supports' => $block_supports,
'render_callback' => 'oribi_render_stat_card',
] );
/* Link Section (parent) */
register_block_type( 'oribi/link-section', [
'attributes' => oribi_card_section_attributes( 3 ),
'supports' => $block_supports,
'render_callback' => 'oribi_render_link_section',
] );
/* Link Card (child) */
register_block_type( 'oribi/link-card', [
'attributes' => array_merge(
[
'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' => '' ],
],
oribi_card_image_attributes()
),
'supports' => $block_supports,
'render_callback' => 'oribi_render_link_card',
] );
/* Pricing Section (parent) */
register_block_type( 'oribi/pricing-section', [
'attributes' => [
'variant' => [ 'type' => 'string', 'default' => 'normal' ],
'label' => [ 'type' => 'string', 'default' => '' ],
'heading' => [ 'type' => 'string', 'default' => '' ],
'lead' => [ 'type' => 'string', 'default' => '' ],
],
'supports' => $block_supports,
'render_callback' => 'oribi_render_pricing_section',
] );
/* Pricing Card (child) */
register_block_type( 'oribi/pricing-card', [
'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 ],
],
'supports' => $block_supports,
'render_callback' => 'oribi_render_pricing_card',
] );
/* Platform Section (parent) */
register_block_type( 'oribi/platform-section', [
'attributes' => [
'label' => [ 'type' => 'string', 'default' => '' ],
'heading' => [ 'type' => 'string', 'default' => '' ],
'lead' => [ 'type' => 'string', 'default' => '' ],
],
'supports' => $block_supports,
'render_callback' => 'oribi_render_platform_section',
] );
/* Platform Row (child) */
register_block_type( 'oribi/platform-row', [
'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 ],
'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 ],
'industryHospitality' => [ 'type' => 'boolean', 'default' => false ],
'industryRetail' => [ 'type' => 'boolean', 'default' => false ],
'industryCorporate' => [ 'type' => 'boolean', 'default' => false ],
'industryEducation' => [ 'type' => 'boolean', 'default' => false ],
'industryOutdoor' => [ 'type' => 'boolean', 'default' => false ],
'industryLiveData' => [ 'type' => 'boolean', 'default' => false ],
],
'supports' => $block_supports,
'render_callback' => 'oribi_render_platform_row',
] );
/* FAQ Section (parent) */
register_block_type( 'oribi/faq-section', [
'attributes' => [
'variant' => [ 'type' => 'string', 'default' => 'normal' ],
'label' => [ 'type' => 'string', 'default' => '' ],
'heading' => [ 'type' => 'string', 'default' => '' ],
'lead' => [ 'type' => 'string', 'default' => '' ],
],
'supports' => $block_supports,
'render_callback' => 'oribi_render_faq_section',
] );
/* FAQ Item (child) */
register_block_type( 'oribi/faq-item', [
'attributes' => [
'question' => [ 'type' => 'string', 'default' => '' ],
'answer' => [ 'type' => 'string', 'default' => '' ],
],
'supports' => $block_supports,
'render_callback' => 'oribi_render_faq_item',
] );
/* Comparison Table */
register_block_type( 'oribi/comparison-table', [
'attributes' => [
'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' => [] ],
],
'supports' => $block_supports,
'render_callback' => 'oribi_render_comparison_table',
] );
/* Trust Section (parent) */
register_block_type( 'oribi/trust-section', [
'attributes' => [
'label' => [ 'type' => 'string', 'default' => '' ],
'heading' => [ 'type' => 'string', 'default' => '' ],
'lead' => [ 'type' => 'string', 'default' => '' ],
'btnText' => [ 'type' => 'string', 'default' => '' ],
'btnUrl' => [ 'type' => 'string', 'default' => '' ],
'btnSub' => [ 'type' => 'string', 'default' => '' ],
],
'supports' => $block_supports,
'render_callback' => 'oribi_render_trust_section',
] );
/* Trust Item (child) */
register_block_type( 'oribi/trust-item', [
'attributes' => [
'heading' => [ 'type' => 'string', 'default' => '' ],
'description' => [ 'type' => 'string', 'default' => '' ],
],
'supports' => $block_supports,
'render_callback' => 'oribi_render_trust_item',
] );
/* ── ANIMATED HERO BLOCKS (OTS Signs) ─────────────────────────────────── */
/* Animated Hero - full homepage hero with particle background */
register_block_type( 'oribi/hero-animated', [
'attributes' => [
'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' => '' ],
],
'supports' => $block_supports,
'render_callback' => 'oribi_render_hero_animated',
] );
/* Animated Page Hero - inner pages with particle background */
register_block_type( 'oribi/page-hero-animated', [
'attributes' => [
'label' => [ 'type' => 'string', 'default' => '' ],
'title' => [ 'type' => 'string', 'default' => '' ],
'description' => [ 'type' => 'string', 'default' => '' ],
],
'supports' => $block_supports,
'render_callback' => 'oribi_render_page_hero_animated',
] );
/* Use Cases Showcase - 4 animated circles beneath the homepage hero */
register_block_type( 'oribi/use-cases', [
'attributes' => [
'label' => [ 'type' => 'string', 'default' => '● Use Cases' ],
'heading' => [ 'type' => 'string', 'default' => 'Built for Every Scenario' ],
'lead' => [ 'type' => 'string', 'default' => 'From menus to meeting-room dashboards, digital signage adapts to your environment.' ],
'case1Title' => [ 'type' => 'string', 'default' => 'Menu Boards' ],
'case1Desc' => [ 'type' => 'string', 'default' => 'Showcase food, drinks, and daily specials with dynamic, always-current displays.' ],
'case2Title' => [ 'type' => 'string', 'default' => 'Event Displays' ],
'case2Desc' => [ 'type' => 'string', 'default' => 'Promote schedules, speakers, and live countdowns across lobbies and venues.' ],
'case3Title' => [ 'type' => 'string', 'default' => 'Office Dashboards' ],
'case3Desc' => [ 'type' => 'string', 'default' => 'Surface KPIs, occupancy data, and team alerts on screens throughout your workspace.' ],
'case4Title' => [ 'type' => 'string', 'default' => 'Wayfinding' ],
'case4Desc' => [ 'type' => 'string', 'default' => 'Guide visitors through complex buildings with interactive maps and directional signs.' ],
],
'supports' => $block_supports,
'render_callback' => 'oribi_render_use_cases',
] );
} );
/* ══════════════════════════════════════════════════════════════════════════════
RENDER CALLBACKS
══════════════════════════════════════════════════════════════════════════════ */
/* ── Site Header ───────────────────────────────────────────────────────────── */
function oribi_render_site_header() {
$has_logo = has_custom_logo();
ob_start(); ?>
<header class="site-header" id="site-header">
<div class="container header-inner">
<div class="site-logo">
<?php if ( $has_logo ) : ?>
<?php the_custom_logo(); ?>
<?php else : ?>
<a href="<?php echo esc_url( home_url( '/' ) ); ?>" class="logo-text"><strong>Oribi</strong> Tech</a>
<?php endif; ?>
</div>
<button class="nav-toggle" id="nav-toggle" aria-expanded="false" aria-label="<?php esc_attr_e( 'Toggle navigation', 'ots-theme' ); ?>">
<span></span><span></span><span></span>
</button>
<nav class="site-nav" id="site-nav">
<?php
wp_nav_menu( [
'theme_location' => 'primary',
'container' => false,
'menu_class' => 'nav-menu',
'depth' => 2,
'fallback_cb' => 'oribi_fallback_menu',
] );
?>
</nav>
<div class="header-cta">
<button id="theme-toggle" class="theme-toggle" aria-label="<?php esc_attr_e( 'Switch to dark mode', 'ots-theme' ); ?>">
<svg class="theme-toggle-icon sun-icon" viewBox="0 0 20 20" fill="currentColor"><path d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"/></svg>
<svg class="theme-toggle-icon moon-icon" viewBox="0 0 20 20" fill="currentColor"><path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"/></svg>
</button>
</div>
</div>
</header>
<?php return ob_get_clean();
}
/** Fallback menu when no WP menu is assigned. */
function oribi_fallback_menu() {
echo '<ul class="nav-menu">';
echo '<li><a href="' . esc_url( home_url( '/solutions' ) ) . '">Solutions</a></li>';
echo '<li><a href="' . esc_url( home_url( '/features' ) ) . '">Features</a></li>';
echo '<li><a href="' . esc_url( home_url( '/pricing' ) ) . '">Pricing</a></li>';
echo '<li><a href="' . esc_url( home_url( '/about' ) ) . '">About</a></li>';
echo '<li><a href="' . esc_url( home_url( '/contact' ) ) . '">Contact</a></li>';
echo '</ul>';
}
/* ── Site Footer ───────────────────────────────────────────────────────────── */
function oribi_render_site_footer() {
$year = gmdate( 'Y' );
ob_start(); ?>
<footer class="site-footer">
<div class="container">
<div class="footer-inner">
<div class="footer-brand">
<?php if ( has_custom_logo() ) : ?>
<?php the_custom_logo(); ?>
<?php else : ?>
<div class="logo-text"><strong>Oribi</strong> Tech</div>
<?php endif; ?>
<p class="footer-tagline">Digital signage solutions that communicate, engage, and grow your business.</p>
<p class="footer-location">An Oribi Technology Services Company</p>
</div>
<div class="footer-links">
<div class="footer-col">
<h4>Platform</h4>
<ul>
<li><a href="<?php echo esc_url( home_url( '/features' ) ); ?>">Features</a></li>
<li><a href="<?php echo esc_url( home_url( '/pricing' ) ); ?>">Pricing</a></li>
<li><a href="<?php echo esc_url( home_url( '/devices' ) ); ?>">Devices</a></li>
<li><a href="<?php echo esc_url( home_url( '/demo' ) ); ?>">Demo</a></li>
</ul>
</div>
<div class="footer-col">
<h4>Company</h4>
<ul>
<li><a href="<?php echo esc_url( home_url( '/about' ) ); ?>">About</a></li>
<li><a href="<?php echo esc_url( home_url( '/solutions' ) ); ?>">Solutions</a></li>
<li><a href="<?php echo esc_url( home_url( '/resources' ) ); ?>">Resources</a></li>
<li><a href="<?php echo esc_url( home_url( '/faq' ) ); ?>">FAQ</a></li>
</ul>
</div>
<div class="footer-col">
<h4>Connect</h4>
<ul>
<li><a href="<?php echo esc_url( home_url( '/contact' ) ); ?>">Contact</a></li>
<li><a href="https://ots-signs.com/portal" target="_blank" rel="noopener">Client Portal</a></li>
<li><a href="mailto:hello@ots-signs.com">Email Us</a></li>
</ul>
</div>
</div>
</div>
<div class="footer-bottom">
<p>&copy; <?php echo esc_html( $year ); ?> Oribi Technology Services. All rights reserved.</p>
</div>
</div>
</footer>
<button id="scroll-top" class="scroll-top" aria-label="<?php esc_attr_e( 'Scroll to top', 'ots-theme' ); ?>">&#8593;</button>
<?php return ob_get_clean();
}
/** Helper: highlight a word in a string. */
function oribi_highlight( $text, $word ) {
if ( ! $word ) return wp_kses_post( $text );
return str_replace(
esc_html( $word ),
'<span class="highlight">' . esc_html( $word ) . '</span>',
wp_kses_post( $text )
);
}
/* ── Hero ──────────────────────────────────────────────────────────────────── */
function oribi_render_hero( $a ) {
ob_start();
?>
<section class="hero">
<div class="container hero-inner">
<div class="hero-content">
<span class="hero-label"><?php echo wp_kses_post( $a['label'] ); ?></span>
<h1 class="hero-title"><?php echo oribi_highlight( $a['title'], $a['highlightWord'] ); ?></h1>
<p class="hero-description"><?php echo wp_kses_post( $a['description'] ); ?></p>
<div class="btn-group">
<a href="<?php echo esc_url( $a['primaryBtnUrl'] ); ?>" class="btn btn-primary btn-lg"><?php echo esc_html( $a['primaryBtnText'] ); ?></a>
<?php if ( $a['secondaryBtnUrl'] ) : ?>
<a href="<?php echo esc_url( $a['secondaryBtnUrl'] ); ?>" target="_blank" rel="noopener" class="btn btn-ghost btn-lg"><?php echo esc_html( $a['secondaryBtnText'] ); ?> &rarr;</a>
<?php endif; ?>
</div>
<div class="hero-stats">
<div><div class="hero-stat-value"><?php echo esc_html( $a['stat1Value'] ); ?></div><div class="hero-stat-label"><?php echo esc_html( $a['stat1Label'] ); ?></div></div>
<div><div class="hero-stat-value"><?php echo esc_html( $a['stat2Value'] ); ?></div><div class="hero-stat-label"><?php echo esc_html( $a['stat2Label'] ); ?></div></div>
</div>
</div>
<div class="hero-visual">
<div class="hero-devices">
<!-- Device 1: Laptop -->
<div class="hero-device hero-device--laptop">
<div class="hero-device__frame">
<div class="hero-device__screen">
<div class="hero-device__screen-content">
<div class="hero-device__app-bars">
<div></div><div></div><div></div><div></div>
</div>
</div>
</div>
<div class="hero-device__base"></div>
</div>
<ul class="hero-device__services">
<li class="svc svc--1"><span class="svc__dot"></span> <?php echo esc_html( $a['svcLaptop1'] ); ?></li>
<li class="svc svc--2"><span class="svc__dot"></span> <?php echo esc_html( $a['svcLaptop2'] ); ?></li>
<li class="svc svc--3"><span class="svc__dot"></span> <?php echo esc_html( $a['svcLaptop3'] ); ?></li>
</ul>
</div>
<!-- Device 2: Cloud (365Care) -->
<div class="hero-device hero-device--cloud">
<div class="hero-device__frame">
<div class="hero-device__cloud-icon">
<svg viewBox="0 0 56 40" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path d="M44.8 16.8C44.8 8.4 38 1.6 29.6 1.6c-6.8 0-12.6 4.4-14.6 10.6C8.4 13 3.2 18.6 3.2 25.4c0 7.4 6 13.4 13.4 13.4h27c5.8 0 10.4-4.6 10.4-10.4 0-5.4-4.2-9.8-9.2-10.2z" stroke="var(--color-accent)" stroke-width="2" fill="none"/>
</svg>
<span class="hero-device__cloud-label">365</span>
</div>
</div>
<ul class="hero-device__services">
<li class="svc svc--1"><span class="svc__dot"></span> <?php echo esc_html( $a['svcCloud1'] ); ?></li>
<li class="svc svc--2"><span class="svc__dot"></span> <?php echo esc_html( $a['svcCloud2'] ); ?></li>
<li class="svc svc--3"><span class="svc__dot"></span> <?php echo esc_html( $a['svcCloud3'] ); ?></li>
</ul>
</div>
<!-- Device 3: Desktop Monitor -->
<div class="hero-device hero-device--desktop">
<div class="hero-device__frame">
<div class="hero-device__screen">
<div class="hero-device__screen-content">
<div class="hero-device__dash-row">
<div class="hero-device__dash-card"></div>
<div class="hero-device__dash-card"></div>
</div>
<div class="hero-device__dash-bar"></div>
<div class="hero-device__dash-bar hero-device__dash-bar--short"></div>
</div>
</div>
<div class="hero-device__stand"></div>
<div class="hero-device__stand-base"></div>
</div>
<ul class="hero-device__services">
<li class="svc svc--1"><span class="svc__dot"></span> <?php echo esc_html( $a['svcDesktop1'] ); ?></li>
<li class="svc svc--2"><span class="svc__dot"></span> <?php echo esc_html( $a['svcDesktop2'] ); ?></li>
<li class="svc svc--3"><span class="svc__dot"></span> <?php echo esc_html( $a['svcDesktop3'] ); ?></li>
</ul>
</div>
<!-- Device 4: Phone -->
<div class="hero-device hero-device--phone">
<div class="hero-device__frame">
<div class="hero-device__screen">
<div class="hero-device__screen-content">
<div class="hero-device__notif">
<span class="hero-device__notif-icon">✓</span>
<span class="hero-device__notif-text">Secure</span>
</div>
</div>
</div>
</div>
<ul class="hero-device__services">
<li class="svc svc--1"><span class="svc__dot"></span> <?php echo esc_html( $a['svcPhone1'] ); ?></li>
<li class="svc svc--2"><span class="svc__dot"></span> <?php echo esc_html( $a['svcPhone2'] ); ?></li>
</ul>
</div>
</div>
</div>
</div>
</section>
<?php return ob_get_clean();
}
/* ── Page Hero ─────────────────────────────────────────────────────────────── */
function oribi_render_page_hero( $a ) {
ob_start(); ?>
<section class="page-hero">
<canvas id="dc-canvas" class="dc-canvas" aria-hidden="true"></canvas>
<div class="hero-overlay"></div>
<div class="container">
<?php if ( $a['label'] ) : ?><span class="hero-label"><?php echo wp_kses_post( $a['label'] ); ?></span><?php endif; ?>
<h1><?php echo wp_kses_post( $a['title'] ); ?></h1>
<p class="lead"><?php echo wp_kses_post( $a['description'] ); ?></p>
</div>
</section>
<?php return ob_get_clean();
}
/* ── CTA Banner ────────────────────────────────────────────────────────────── */
function oribi_render_cta_banner( $a ) {
ob_start(); ?>
<section class="cta-banner">
<div class="container text-center">
<h2><?php echo wp_kses_post( $a['heading'] ); ?></h2>
<p><?php echo wp_kses_post( $a['text'] ); ?></p>
<a href="<?php echo esc_url( $a['btnUrl'] ); ?>" class="btn btn-primary btn-lg" style="background:#fff;color:var(--color-primary);"><?php echo esc_html( $a['btnText'] ); ?></a>
</div>
</section>
<?php return ob_get_clean();
}
/* ── Intro Section ─────────────────────────────────────────────────────────── */
function oribi_render_intro_section( $a ) {
$cls = $a['variant'] === 'alt' ? 'section section-alt' : 'section';
$rev = $a['reversed'] ? ' style="direction:rtl;"' : '';
$ltr = $a['reversed'] ? ' style="direction:ltr;"' : '';
ob_start(); ?>
<section class="<?php echo esc_attr( $cls ); ?>">
<div class="container">
<div class="about-intro"<?php echo $rev; ?>>
<div<?php echo $ltr; ?>>
<?php if ( $a['label'] ) : ?><span class="section-label"><?php echo esc_html( $a['label'] ); ?></span><?php endif; ?>
<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 echo $ltr; ?>><?php echo wp_kses_post( $a['visual'] ); ?></div>
</div>
</div>
</section>
<?php return ob_get_clean();
}
/* ── Contact Section ───────────────────────────────────────────────────────── */
function oribi_render_contact_section( $a ) {
ob_start(); ?>
<section class="section">
<div class="container">
<div class="contact-layout">
<div class="contact-info">
<h2><?php echo wp_kses_post( $a['heading'] ); ?></h2>
<p class="lead"><?php echo wp_kses_post( $a['lead'] ); ?></p>
<div class="contact-method"><div class="contact-method-icon"><i class="fas fa-envelope" aria-hidden="true"></i></div><div><div class="contact-method-label">Email Us</div><div class="contact-method-value"><a href="mailto:<?php echo esc_attr( $a['email'] ); ?>"><?php echo esc_html( $a['email'] ); ?></a></div></div></div>
<div class="contact-method"><div class="contact-method-icon"><i class="fas fa-headset" aria-hidden="true"></i></div><div><div class="contact-method-label">Existing Customer Support</div><div class="contact-method-value"><a href="<?php echo esc_url( $a['supportUrl'] ); ?>" target="_blank" rel="noopener">Open a Support Ticket</a></div></div></div>
<div class="contact-method"><div class="contact-method-icon"><i class="fas fa-globe" aria-hidden="true"></i></div><div><div class="contact-method-label">Client Portal</div><div class="contact-method-value"><a href="<?php echo esc_url( $a['portalUrl'] ); ?>" target="_blank" rel="noopener"><?php echo esc_html( wp_parse_url( $a['portalUrl'], PHP_URL_HOST ) ); ?></a></div></div></div>
<div class="contact-method"><div class="contact-method-icon"><i class="fas fa-map-marker-alt" aria-hidden="true"></i></div><div><div class="contact-method-label">Location</div><div class="contact-method-value"><?php echo esc_html( $a['location'] ); ?></div></div></div>
</div>
<div class="contact-form-wrap">
<h3 style="margin-bottom:1.5rem;"><?php echo esc_html( $a['formHeading'] ); ?></h3>
<form id="contact-form">
<div class="form-row">
<div class="form-group"><label for="cf-name">Name <span class="req">*</span></label><input type="text" id="cf-name" name="name" required></div>
<div class="form-group"><label for="cf-email">Email <span class="req">*</span></label><input type="email" id="cf-email" name="email" required></div>
</div>
<div class="form-group">
<label for="cf-interest">Interested In</label>
<select id="cf-interest" name="interest">
<option value="">Select a topic&hellip;</option>
<option value="Digital Signage">Digital Signage Solutions</option>
<option value="Content Creation">Content Creation</option>
<option value="Hardware">Player Devices &amp; Hardware</option>
<option value="Pricing">Pricing &amp; Plans</option>
<option value="Demo">Request a Demo</option>
<option value="Support">Technical Support</option>
<option value="Other">Other / Not sure</option>
</select>
</div>
<div class="form-group"><label for="cf-message">Message <span class="req">*</span></label><textarea id="cf-message" name="message" required></textarea></div>
<button type="submit" class="btn btn-primary btn-lg" style="width:100%;justify-content:center;">Submit</button>
<div class="form-notice" id="form-notice"></div>
</form>
</div>
</div>
</div>
</section>
<?php return ob_get_clean();
}
/* ── Feature Section (parent - wraps child feature-card blocks) ────────────── */
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'] : '';
ob_start();
if ( $img['html'] && $img['position'] === 'left' ) : ?>
<<?php echo $tag . $href; ?> class="oribi-card<?php echo esc_attr( $link_cls . $img_cls ); ?>">
<?php echo $img['html']; ?>
<div class="oribi-card-body">
<h3><?php echo wp_kses_post( $a['title'] ); ?></h3>
<p><?php echo wp_kses_post( $a['description'] ); ?></p>
</div>
</<?php echo $tag; ?>>
<?php elseif ( $img['html'] && $img['position'] === 'background' ) : ?>
<<?php echo $tag . $href; ?> class="oribi-card<?php echo esc_attr( $link_cls . $img_cls ); ?>">
<?php echo $img['html']; ?>
<div class="oribi-card-body">
<?php if ( oribi_has_icon( $a ) ) : ?><div class="feature-icon"<?php echo $center ? ' style="margin-inline:auto;"' : ''; ?>><?php echo oribi_render_icon( $a ); ?></div><?php endif; ?>
<h3><?php echo wp_kses_post( $a['title'] ); ?></h3>
<p><?php echo wp_kses_post( $a['description'] ); ?></p>
</div>
</<?php echo $tag; ?>>
<?php else : ?>
<<?php echo $tag . $href; ?> class="oribi-card<?php echo esc_attr( $link_cls . $img_cls ); ?><?php echo $center ? ' text-center' : ''; ?>">
<?php if ( $img['html'] && ( $img['position'] === 'top' || $img['position'] === 'replace-icon' ) ) : ?>
<?php echo $img['html']; ?>
<?php endif; ?>
<?php if ( ( ! $img['html'] || $img['position'] !== 'replace-icon' ) && oribi_has_icon( $a ) ) : ?>
<div class="feature-icon"<?php echo $center ? ' style="margin-inline:auto;"' : ''; ?>><?php echo oribi_render_icon( $a ); ?></div>
<?php endif; ?>
<h3><?php echo wp_kses_post( $a['title'] ); ?></h3>
<p><?php echo wp_kses_post( $a['description'] ); ?></p>
</<?php echo $tag; ?>>
<?php endif;
return ob_get_clean();
}
/* ── Value Section ─────────────────────────────────────────────────────────── */
function oribi_render_value_section( $a, $content ) {
return oribi_render_card_section( $a, $content, 'grid', 3 );
}
/* ── Value Card ────────────────────────────────────────────────────────────── */
function oribi_render_value_card( $a ) {
$img = oribi_card_image_html( $a );
$img_cls = $img['card_class'] ? ' ' . $img['card_class'] : '';
ob_start();
if ( $img['html'] && $img['position'] === 'background' ) : ?>
<div class="oribi-card value-card<?php echo esc_attr( $img_cls ); ?>">
<?php echo $img['html']; ?>
<div class="oribi-card-body">
<?php if ( oribi_has_icon( $a ) ) : ?><div class="value-icon"><?php echo oribi_render_icon( $a ); ?></div><?php endif; ?>
<h3><?php echo wp_kses_post( $a['title'] ); ?></h3>
<p><?php echo wp_kses_post( $a['description'] ); ?></p>
</div>
</div>
<?php elseif ( $img['html'] && $img['position'] === 'left' ) : ?>
<div class="oribi-card value-card<?php echo esc_attr( $img_cls ); ?>">
<?php echo $img['html']; ?>
<div class="oribi-card-body">
<h3><?php echo wp_kses_post( $a['title'] ); ?></h3>
<p><?php echo wp_kses_post( $a['description'] ); ?></p>
</div>
</div>
<?php else : ?>
<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 ) ) : ?>
<div class="value-icon"><?php echo oribi_render_icon( $a ); ?></div>
<?php endif; ?>
<h3><?php echo wp_kses_post( $a['title'] ); ?></h3>
<p><?php echo wp_kses_post( $a['description'] ); ?></p>
</div>
<?php endif;
return ob_get_clean();
}
/* ── Addon Section ─────────────────────────────────────────────────────────── */
function oribi_render_addon_section( $a, $content ) {
return oribi_render_card_section( $a, $content, 'grid', 3 );
}
/* ── Addon Card ────────────────────────────────────────────────────────────── */
function oribi_render_addon_card( $a ) {
$img = oribi_card_image_html( $a );
$img_cls = $img['card_class'] ? ' ' . $img['card_class'] : '';
ob_start();
if ( $img['html'] && $img['position'] === 'left' ) : ?>
<div class="oribi-card addon-card<?php echo esc_attr( $img_cls ); ?>">
<?php echo $img['html']; ?>
<div class="oribi-card-body">
<?php if ( ! empty( $a['tag'] ) ) : ?><span class="addon-tag"><?php echo esc_html( $a['tag'] ); ?></span><?php endif; ?>
<h3><?php echo wp_kses_post( $a['title'] ); ?></h3>
<p><?php echo wp_kses_post( $a['description'] ); ?></p>
</div>
</div>
<?php elseif ( $img['html'] && $img['position'] === 'background' ) : ?>
<div class="oribi-card addon-card<?php echo esc_attr( $img_cls ); ?>">
<?php echo $img['html']; ?>
<div class="oribi-card-body">
<?php if ( oribi_has_icon( $a ) ) : ?><div class="feature-icon"><?php echo oribi_render_icon( $a ); ?></div><?php endif; ?>
<?php if ( ! empty( $a['tag'] ) ) : ?><span class="addon-tag"><?php echo esc_html( $a['tag'] ); ?></span><?php endif; ?>
<h3><?php echo wp_kses_post( $a['title'] ); ?></h3>
<p><?php echo wp_kses_post( $a['description'] ); ?></p>
</div>
</div>
<?php else : ?>
<div class="oribi-card addon-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 ) ) : ?>
<div class="feature-icon"><?php echo oribi_render_icon( $a ); ?></div>
<?php endif; ?>
<?php if ( ! empty( $a['tag'] ) ) : ?><span class="addon-tag"><?php echo esc_html( $a['tag'] ); ?></span><?php endif; ?>
<h3><?php echo wp_kses_post( $a['title'] ); ?></h3>
<p><?php echo wp_kses_post( $a['description'] ); ?></p>
</div>
<?php endif;
return ob_get_clean();
}
/* ── Image Section ─────────────────────────────────────────────────────────── */
function oribi_render_image_section( $a, $content ) {
return oribi_render_card_section( $a, $content, 'grid', 3 );
}
/* ── Image Card ────────────────────────────────────────────────────────────── */
function oribi_render_image_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' : '';
$img = oribi_card_image_html( $a );
$img_cls = $img['card_class'] ? ' ' . $img['card_class'] : '';
ob_start();
if ( $img['html'] && $img['position'] === 'left' ) : ?>
<<?php echo $tag . $href; ?> class="oribi-card image-card<?php echo esc_attr( $link_cls . $img_cls ); ?>">
<?php echo $img['html']; ?>
<div class="oribi-card-body">
<h3><?php echo wp_kses_post( $a['title'] ); ?></h3>
<p><?php echo wp_kses_post( $a['description'] ); ?></p>
</div>
</<?php echo $tag; ?>>
<?php elseif ( $img['html'] && $img['position'] === 'background' ) : ?>
<<?php echo $tag . $href; ?> class="oribi-card image-card<?php echo esc_attr( $link_cls . $img_cls ); ?>">
<?php echo $img['html']; ?>
<div class="oribi-card-body">
<?php if ( oribi_has_icon( $a ) ) : ?><div class="feature-icon"><?php echo oribi_render_icon( $a ); ?></div><?php endif; ?>
<h3><?php echo wp_kses_post( $a['title'] ); ?></h3>
<p><?php echo wp_kses_post( $a['description'] ); ?></p>
</div>
</<?php echo $tag; ?>>
<?php else : ?>
<<?php echo $tag . $href; ?> class="oribi-card image-card<?php echo esc_attr( $link_cls . $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 ) ) : ?>
<div class="feature-icon"><?php echo oribi_render_icon( $a ); ?></div>
<?php endif; ?>
<div class="oribi-card-body">
<h3><?php echo wp_kses_post( $a['title'] ); ?></h3>
<p><?php echo wp_kses_post( $a['description'] ); ?></p>
</div>
</<?php echo $tag; ?>>
<?php endif;
return ob_get_clean();
}
/* ── Stat Section ──────────────────────────────────────────────────────────── */
function oribi_render_stat_section( $a, $content ) {
return oribi_render_card_section( $a, $content, 'grid', 3 );
}
/* ── Stat Card ─────────────────────────────────────────────────────────────── */
function oribi_render_stat_card( $a ) {
$img = oribi_card_image_html( $a );
$img_cls = $img['card_class'] ? ' ' . $img['card_class'] : '';
ob_start();
if ( $img['html'] && $img['position'] === 'background' ) : ?>
<div class="oribi-card stat-card<?php echo esc_attr( $img_cls ); ?>">
<?php echo $img['html']; ?>
<div class="oribi-card-body">
<?php if ( oribi_has_icon( $a ) ) : ?><div class="feature-icon"><?php echo oribi_render_icon( $a ); ?></div><?php endif; ?>
<div class="stat-value"><?php echo esc_html( $a['value'] ?? '' ); ?></div>
<div class="stat-label"><?php echo esc_html( $a['label'] ?? '' ); ?></div>
<?php if ( ! empty( $a['description'] ) ) : ?><p><?php echo wp_kses_post( $a['description'] ); ?></p><?php endif; ?>
</div>
</div>
<?php elseif ( $img['html'] && $img['position'] === 'left' ) : ?>
<div class="oribi-card stat-card<?php echo esc_attr( $img_cls ); ?>">
<?php echo $img['html']; ?>
<div class="oribi-card-body">
<div class="stat-value"><?php echo esc_html( $a['value'] ?? '' ); ?></div>
<div class="stat-label"><?php echo esc_html( $a['label'] ?? '' ); ?></div>
<?php if ( ! empty( $a['description'] ) ) : ?><p><?php echo wp_kses_post( $a['description'] ); ?></p><?php endif; ?>
</div>
</div>
<?php else : ?>
<div class="oribi-card stat-card<?php echo esc_attr( $img_cls ); ?>">
<?php if ( $img['html'] ) echo $img['html']; ?>
<?php if ( oribi_has_icon( $a ) ) : ?><div class="feature-icon"><?php echo oribi_render_icon( $a ); ?></div><?php endif; ?>
<div class="stat-value"><?php echo esc_html( $a['value'] ?? '' ); ?></div>
<div class="stat-label"><?php echo esc_html( $a['label'] ?? '' ); ?></div>
<?php if ( ! empty( $a['description'] ) ) : ?><p><?php echo wp_kses_post( $a['description'] ); ?></p><?php endif; ?>
</div>
<?php endif;
return ob_get_clean();
}
/* ── Link Section ──────────────────────────────────────────────────────────── */
function oribi_render_link_section( $a, $content ) {
return oribi_render_card_section( $a, $content, 'grid', 3 );
}
/* ── Link Card ─────────────────────────────────────────────────────────────── */
function oribi_render_link_card( $a ) {
$img = oribi_card_image_html( $a );
$img_cls = $img['card_class'] ? ' ' . $img['card_class'] : '';
$cta = '';
if ( ! empty( $a['linkUrl'] ) ) {
$cta = '<a href="' . esc_url( $a['linkUrl'] ) . '" class="link-card-cta">'
. esc_html( $a['linkText'] ?? 'Learn More' ) . '</a>';
}
ob_start();
if ( $img['html'] && $img['position'] === 'left' ) : ?>
<div class="oribi-card link-card<?php echo esc_attr( $img_cls ); ?>">
<?php echo $img['html']; ?>
<div class="oribi-card-body">
<?php if ( oribi_has_icon( $a ) ) : ?><div class="feature-icon"><?php echo oribi_render_icon( $a ); ?></div><?php endif; ?>
<h3><?php echo wp_kses_post( $a['title'] ); ?></h3>
<p><?php echo wp_kses_post( $a['description'] ); ?></p>
<?php echo $cta; ?>
</div>
</div>
<?php elseif ( $img['html'] && $img['position'] === 'background' ) : ?>
<div class="oribi-card link-card<?php echo esc_attr( $img_cls ); ?>">
<?php echo $img['html']; ?>
<div class="oribi-card-body">
<?php if ( oribi_has_icon( $a ) ) : ?><div class="feature-icon"><?php echo oribi_render_icon( $a ); ?></div><?php endif; ?>
<h3><?php echo wp_kses_post( $a['title'] ); ?></h3>
<p><?php echo wp_kses_post( $a['description'] ); ?></p>
<?php echo $cta; ?>
</div>
</div>
<?php else : ?>
<div class="oribi-card link-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 ) ) : ?>
<div class="feature-icon"><?php echo oribi_render_icon( $a ); ?></div>
<?php endif; ?>
<h3><?php echo wp_kses_post( $a['title'] ); ?></h3>
<p><?php echo wp_kses_post( $a['description'] ); ?></p>
<?php echo $cta; ?>
</div>
<?php endif;
return ob_get_clean();
}
/* ── Pricing Section (parent - wraps child pricing-card blocks) ────────────── */
function oribi_render_pricing_section( $a, $content, $block ) {
$cls = $a['variant'] === 'alt' ? 'section section-alt' : 'section';
$count = count( $block->inner_blocks );
ob_start(); ?>
<section class="<?php echo esc_attr( $cls ); ?>">
<div class="container">
<div class="section-header">
<?php if ( $a['label'] ) : ?><span class="section-label"><?php echo esc_html( $a['label'] ); ?></span><?php endif; ?>
<h2><?php echo wp_kses_post( $a['heading'] ); ?></h2>
<?php if ( $a['lead'] ) : ?><p class="lead"><?php echo wp_kses_post( $a['lead'] ); ?></p><?php endif; ?>
</div>
<div class="pricing-grid pricing-grid-<?php echo intval( $count ); ?>">
<?php echo $content; ?>
</div>
</div>
</section>
<?php return ob_get_clean();
}
/* ── Pricing Card (child - renders one pricing tier) ───────────────────────── */
function oribi_render_pricing_card( $a ) {
$featured = ! empty( $a['featured'] );
$img_id = ! empty( $a['imgId'] ) ? intval( $a['imgId'] ) : 0;
$img_url = ! empty( $a['imgUrl'] ) ? $a['imgUrl'] : '';
$img_alt = ! empty( $a['imgAlt'] ) ? $a['imgAlt'] : '';
$img_w = ! empty( $a['imgWidth'] ) ? intval( $a['imgWidth'] ) : 80;
$img_html = '';
if ( $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 ) {
$img_html = wp_get_attachment_image( $img_id, 'full', false, [
'class' => 'card-image',
'style' => $img_style,
'alt' => $img_alt,
] );
} else {
$img_html = '<img class="card-image" src="' . esc_url( $img_url ) . '" alt="' . esc_attr( $img_alt ) . '" style="' . esc_attr( $img_style ) . '">';
}
}
ob_start(); ?>
<div class="pricing-card<?php echo $featured ? ' featured' : ''; ?>">
<?php if ( $featured && ! empty( $a['badge'] ) ) : ?>
<span class="pricing-badge"><?php echo esc_html( $a['badge'] ); ?></span>
<?php endif; ?>
<?php if ( $img_html ) : ?>
<div class="card-image-wrap" style="text-align:center;margin-bottom:1.25rem;"><?php echo $img_html; ?></div>
<?php endif; ?>
<?php if ( oribi_has_icon( $a ) ) : ?><div class="feature-icon" style="margin-inline:auto;"><?php echo oribi_render_icon( $a ); ?></div><?php endif; ?>
<div class="pricing-name"><?php echo esc_html( $a['name'] ); ?></div>
<p class="pricing-tagline"><?php echo wp_kses_post( $a['tagline'] ); ?></p>
<?php if ( ! empty( $a['price'] ) ) : ?>
<div class="pricing-price">
<div class="pricing-amount"><?php echo wp_kses_post( $a['price'] ); ?></div>
<?php if ( ! empty( $a['pricePer'] ) ) : ?><div class="pricing-per"><?php echo wp_kses_post( $a['pricePer'] ); ?></div><?php endif; ?>
</div>
<?php endif; ?>
<ul class="pricing-features">
<?php foreach ( ( $a['features'] ?? [] ) as $f ) : ?>
<li><span class="pricing-check">&#10003;</span> <?php echo wp_kses_post( $f ); ?></li>
<?php endforeach; ?>
</ul>
<a href="<?php echo esc_url( $a['btnUrl'] ?? '' ); ?>" class="btn <?php echo $featured ? 'btn-primary' : 'btn-outline'; ?>" style="width:100%;justify-content:center;"><?php echo esc_html( $a['btnText'] ?? 'Get Started' ); ?></a>
</div>
<?php return ob_get_clean();
}
/* ── Platform Section (parent - wraps child platform-row blocks) ───────────── */
function oribi_render_platform_section( $a, $content ) {
ob_start(); ?>
<section class="section">
<div class="container">
<div class="section-header">
<?php if ( $a['label'] ) : ?><span class="section-label"><?php echo esc_html( $a['label'] ); ?></span><?php endif; ?>
<h2><?php echo wp_kses_post( $a['heading'] ); ?></h2>
<?php if ( $a['lead'] ) : ?><p class="lead"><?php echo wp_kses_post( $a['lead'] ); ?></p><?php endif; ?>
</div>
<?php echo $content; ?>
</div>
</section>
<?php return ob_get_clean();
}
/* ── Platform Row (child - renders one service row) ────────────────────────── */
function oribi_render_platform_row( $a ) {
$rev = ! empty( $a['reversed'] ) ? ' reverse' : '';
$img_id = ! empty( $a['imgId'] ) ? intval( $a['imgId'] ) : 0;
$img_url = ! empty( $a['imgUrl'] ) ? $a['imgUrl'] : '';
$img_alt = ! empty( $a['imgAlt'] ) ? $a['imgAlt'] : '';
$img_w = ! empty( $a['imgWidth'] ) ? intval( $a['imgWidth'] ) : 300;
// Only render animated dashboard when explicitly flagged
$is_dashboard = ! empty( $a['isDashboard'] );
if ( $is_dashboard ) {
// Render animated dashboard chart SVG
// Text uses class hooks: .ct = title, .cl = label, .cv = value
// JS will dynamically set fill colours based on data-theme
$visual_html = '<div class="dashboard-tv" data-dashboard-container="true">
<div class="dashboard-tv__body">
<div class="dashboard-tv__screen">
<svg viewBox="0 0 800 450" xmlns="http://www.w3.org/2000/svg" class="dashboard-chart" role="img" aria-label="Animated dashboard charts">
<defs>
<linearGradient id="barGradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#D83302" stop-opacity="1"/>
<stop offset="100%" stop-color="#4CAF50" stop-opacity=".8"/>
</linearGradient>
<linearGradient id="lineGradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#4CAF50" stop-opacity=".3"/>
<stop offset="100%" stop-color="#4CAF50" stop-opacity="0"/>
</linearGradient>
</defs>
<!-- ── Top-left: Performance bars ── -->
<g transform="translate(35,20)">
<text class="ct" x="0" y="0" font-size="14" font-weight="600" fill="#333">Performance</text>
<g id="bars-group-1" transform="translate(0,25)">
<rect class="bar" x="0" y="120" width="28" height="0" fill="url(#barGradient)"/>
<rect class="bar" x="40" y="120" width="28" height="0" fill="url(#barGradient)"/>
<rect class="bar" x="80" y="120" width="28" height="0" fill="url(#barGradient)"/>
<rect class="bar" x="120" y="120" width="28" height="0" fill="url(#barGradient)"/>
<rect class="bar" x="160" y="120" width="28" height="0" fill="url(#barGradient)"/>
</g>
<g transform="translate(0,152)">
<text class="cl" x="14" y="0" font-size="10" text-anchor="middle" fill="#666">API</text>
<text class="cl" x="54" y="0" font-size="10" text-anchor="middle" fill="#666">Cache</text>
<text class="cl" x="94" y="0" font-size="10" text-anchor="middle" fill="#666">DB</text>
<text class="cl" x="134" y="0" font-size="10" text-anchor="middle" fill="#666">Queue</text>
<text class="cl" x="174" y="0" font-size="10" text-anchor="middle" fill="#666">Worker</text>
</g>
<g id="values-group-1" transform="translate(0,168)">
<text class="cv" x="14" y="0" font-size="11" font-weight="600" text-anchor="middle" fill="#333">0%</text>
<text class="cv" x="54" y="0" font-size="11" font-weight="600" text-anchor="middle" fill="#333">0%</text>
<text class="cv" x="94" y="0" font-size="11" font-weight="600" text-anchor="middle" fill="#333">0%</text>
<text class="cv" x="134" y="0" font-size="11" font-weight="600" text-anchor="middle" fill="#333">0%</text>
<text class="cv" x="174" y="0" font-size="11" font-weight="600" text-anchor="middle" fill="#333">0%</text>
</g>
</g>
<!-- ── Top-right: Requests/sec bars ── -->
<g transform="translate(430,20)">
<text class="ct" x="0" y="0" font-size="14" font-weight="600" fill="#333">Requests/sec</text>
<g id="bars-group-2" transform="translate(0,25)">
<rect class="bar" x="0" y="120" width="28" height="0" fill="url(#barGradient)"/>
<rect class="bar" x="40" y="120" width="28" height="0" fill="url(#barGradient)"/>
<rect class="bar" x="80" y="120" width="28" height="0" fill="url(#barGradient)"/>
<rect class="bar" x="120" y="120" width="28" height="0" fill="url(#barGradient)"/>
</g>
<g transform="translate(0,152)">
<text class="cl" x="14" y="0" font-size="10" text-anchor="middle" fill="#666">Read</text>
<text class="cl" x="54" y="0" font-size="10" text-anchor="middle" fill="#666">Write</text>
<text class="cl" x="94" y="0" font-size="10" text-anchor="middle" fill="#666">Update</text>
<text class="cl" x="134" y="0" font-size="10" text-anchor="middle" fill="#666">Delete</text>
</g>
<g id="values-group-2" transform="translate(0,168)">
<text class="cv" x="14" y="0" font-size="11" font-weight="600" text-anchor="middle" fill="#333">0</text>
<text class="cv" x="54" y="0" font-size="11" font-weight="600" text-anchor="middle" fill="#333">0</text>
<text class="cv" x="94" y="0" font-size="11" font-weight="600" text-anchor="middle" fill="#333">0</text>
<text class="cv" x="134" y="0" font-size="11" font-weight="600" text-anchor="middle" fill="#333">0</text>
</g>
</g>
<!-- ── Bottom-left: Traffic Trend line ── -->
<g id="line-graph" transform="translate(35,245)">
<text class="ct" x="0" y="0" font-size="14" font-weight="600" fill="#333">Traffic Trend</text>
<g transform="translate(0,25)">
<line class="grid-line" x1="0" y1="0" x2="340" y2="0" stroke="#E0E0E0" stroke-width=".5"/>
<line class="grid-line" x1="0" y1="40" x2="340" y2="40" stroke="#E0E0E0" stroke-width=".5"/>
<line class="grid-line" x1="0" y1="80" x2="340" y2="80" stroke="#E0E0E0" stroke-width=".5"/>
<line class="grid-line" x1="0" y1="120" x2="340" y2="120" stroke="#E0E0E0" stroke-width=".5"/>
</g>
<g transform="translate(0,25)">
<path id="line-fill" d="M0,80 L340,80 L340,145 L0,145 Z" fill="url(#lineGradient)"/>
<path id="line-path" d="M0,80 L340,80" stroke="#4CAF50" stroke-width="2.5" fill="none" stroke-linecap="round"/>
</g>
</g>
<!-- ── Bottom-right: Distribution pie ── -->
<g transform="translate(490,245)">
<text class="ct" x="100" y="0" font-size="14" font-weight="600" text-anchor="middle" fill="#333">Distribution</text>
<g transform="translate(100,90)">
<g class="pie-segment" transform="rotate(0)">
<path d="M0,0 L0,-55 A55,55 0 0,1 38.89,-38.89 Z" fill="#D83302" opacity=".9"/>
</g>
<g class="pie-segment" transform="rotate(90)">
<path d="M0,0 L0,-55 A55,55 0 0,1 38.89,-38.89 Z" fill="#4CAF50" opacity=".8"/>
</g>
<g class="pie-segment" transform="rotate(180)">
<path d="M0,0 L0,-55 A55,55 0 0,1 38.89,-38.89 Z" fill="#f59e0b" opacity=".7"/>
</g>
<g class="pie-segment" transform="rotate(270)">
<path d="M0,0 L0,-55 A55,55 0 0,1 38.89,-38.89 Z" fill="#ef4444" opacity=".7"/>
</g>
<circle id="pie-center" cx="0" cy="0" r="22" fill="#fff" stroke="#E0E0E0" stroke-width="1"/>
<text id="pie-center-text" x="0" y="5" text-anchor="middle" font-size="13" font-weight="600" fill="#333">100%</text>
</g>
<g transform="translate(30,170)">
<rect x="0" y="0" width="8" height="8" fill="#D83302"/>
<text class="cl" x="12" y="8" font-size="11" fill="#666">Service A</text>
<rect x="0" y="15" width="8" height="8" fill="#4CAF50"/>
<text class="cl" x="12" y="23" font-size="11" fill="#666">Service B</text>
<rect x="100" y="0" width="8" height="8" fill="#f59e0b"/>
<text class="cl" x="112" y="8" font-size="11" fill="#666">Service C</text>
<rect x="100" y="15" width="8" height="8" fill="#ef4444"/>
<text class="cl" x="112" y="23" font-size="11" fill="#666">Service D</text>
</g>
</g>
</svg>
</div></div>
<div class="dashboard-tv__feet"><div class="dashboard-tv__foot"></div><div class="dashboard-tv__foot"></div></div>
</div>';
$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 = '<img src="' . esc_url( $img_url ) . '" alt="' . esc_attr( $img_alt ) . '" style="' . esc_attr( $img_style ) . '">';
}
$visual_cls = 'platform-visual has-img';
} elseif ( ! empty( $a['deviceAnim'] ) ) {
$da = '<div class="da-stage" aria-hidden="true">';
$da .= '<div class="da-device da-tablet"><div class="da-body"><div class="da-screen"></div></div><span class="da-label">Tablet</span></div>';
$da .= '<div class="da-device da-monitor-sm"><div class="da-body"><div class="da-screen"></div></div><div class="da-stand"><div class="da-stem"></div><div class="da-base"></div></div><span class="da-label">Small Monitor</span></div>';
$da .= '<div class="da-device da-monitor-lg"><div class="da-body"><div class="da-screen"></div></div><div class="da-stand"><div class="da-stem"></div><div class="da-base"></div></div><span class="da-label">Large Monitor</span></div>';
$da .= '<div class="da-device da-tv"><div class="da-body"><div class="da-screen"></div></div><div class="da-feet"><div class="da-foot"></div><div class="da-foot"></div></div><span class="da-label">TV</span></div>';
$da .= '<div class="da-device da-projector"><div class="da-proj-layout"><div class="da-proj-body"><div class="da-lens"></div></div><div class="da-beam"></div><div class="da-proj-screen"><div class="da-screen"></div></div></div><span class="da-label">Projector</span></div>';
$da .= '<div class="da-device da-vwall"><div class="da-vwall-grid"><div class="da-panel"><div class="da-screen"></div></div><div class="da-panel"><div class="da-screen"></div></div><div class="da-panel"><div class="da-screen"></div></div><div class="da-panel"><div class="da-screen"></div></div></div><span class="da-label">Video Wall</span></div>';
$da .= '</div>';
$visual_html = $da;
$visual_cls = 'platform-visual has-anim';
} elseif ( ! empty( $a['tvStick'] ) ) {
$ts = '<div class="ts-stage" data-tv-stick-anim aria-hidden="true">';
$ts .= '<div class="ts-tv">';
$ts .= '<div class="ts-tv__body">';
$ts .= '<div class="ts-tv__screen">';
$ts .= '<div class="ts-slides">';
// Slide 1 — Menu Board
$ts .= '<div class="ts-slide ts-slide--menu">';
$ts .= '<div class="ts-menu">';
$ts .= '<div class="ts-menu__header"><div class="ts-menu__logo"></div><div class="ts-menu__title"></div></div>';
$ts .= '<div class="ts-menu__cols">';
$ts .= '<div class="ts-menu__col">';
$ts .= '<div class="ts-menu__cat"></div>';
$ts .= '<div class="ts-menu__item"><span class="ts-menu__name"></span><span class="ts-menu__dots"></span><span class="ts-menu__price"></span></div>';
$ts .= '<div class="ts-menu__item"><span class="ts-menu__name"></span><span class="ts-menu__dots"></span><span class="ts-menu__price"></span></div>';
$ts .= '<div class="ts-menu__item"><span class="ts-menu__name"></span><span class="ts-menu__dots"></span><span class="ts-menu__price"></span></div>';
$ts .= '</div>';
$ts .= '<div class="ts-menu__col">';
$ts .= '<div class="ts-menu__cat"></div>';
$ts .= '<div class="ts-menu__item"><span class="ts-menu__name"></span><span class="ts-menu__dots"></span><span class="ts-menu__price"></span></div>';
$ts .= '<div class="ts-menu__item"><span class="ts-menu__name"></span><span class="ts-menu__dots"></span><span class="ts-menu__price"></span></div>';
$ts .= '<div class="ts-menu__item"><span class="ts-menu__name"></span><span class="ts-menu__dots"></span><span class="ts-menu__price"></span></div>';
$ts .= '</div>';
$ts .= '</div>';
$ts .= '</div>';
$ts .= '</div>';
// Slide 2 — Wayfinding Sign
$ts .= '<div class="ts-slide ts-slide--wayfind">';
$ts .= '<div class="ts-wf">';
$ts .= '<div class="ts-wf__header"><div class="ts-wf__building"></div></div>';
$ts .= '<div class="ts-wf__rows">';
$ts .= '<div class="ts-wf__row"><span class="ts-wf__arrow ts-wf__arrow--left"></span><span class="ts-wf__label ts-wf__label--w1"></span><span class="ts-wf__floor"></span></div>';
$ts .= '<div class="ts-wf__row"><span class="ts-wf__arrow ts-wf__arrow--right"></span><span class="ts-wf__label ts-wf__label--w2"></span><span class="ts-wf__floor"></span></div>';
$ts .= '<div class="ts-wf__row"><span class="ts-wf__arrow ts-wf__arrow--up"></span><span class="ts-wf__label ts-wf__label--w3"></span><span class="ts-wf__floor"></span></div>';
$ts .= '<div class="ts-wf__row"><span class="ts-wf__arrow ts-wf__arrow--left"></span><span class="ts-wf__label ts-wf__label--w4"></span><span class="ts-wf__floor"></span></div>';
$ts .= '</div>';
$ts .= '</div>';
$ts .= '</div>';
// Slide 3 — Schedule / Timetable
$ts .= '<div class="ts-slide ts-slide--sched">';
$ts .= '<div class="ts-sched">';
$ts .= '<div class="ts-sched__header"><div class="ts-sched__title"></div><div class="ts-sched__date"></div></div>';
$ts .= '<div class="ts-sched__table">';
$ts .= '<div class="ts-sched__hrow"><span class="ts-sched__hcell"></span><span class="ts-sched__hcell"></span><span class="ts-sched__hcell"></span><span class="ts-sched__hcell"></span></div>';
$ts .= '<div class="ts-sched__row"><span class="ts-sched__time"></span><span class="ts-sched__event ts-sched__event--a"></span><span class="ts-sched__cell"></span><span class="ts-sched__cell"></span></div>';
$ts .= '<div class="ts-sched__row"><span class="ts-sched__time"></span><span class="ts-sched__cell"></span><span class="ts-sched__event ts-sched__event--b"></span><span class="ts-sched__cell"></span></div>';
$ts .= '<div class="ts-sched__row"><span class="ts-sched__time"></span><span class="ts-sched__cell"></span><span class="ts-sched__cell"></span><span class="ts-sched__event ts-sched__event--c"></span></div>';
$ts .= '<div class="ts-sched__row"><span class="ts-sched__time"></span><span class="ts-sched__event ts-sched__event--a"></span><span class="ts-sched__event ts-sched__event--b"></span><span class="ts-sched__cell"></span></div>';
$ts .= '</div>';
$ts .= '</div>';
$ts .= '</div>';
$ts .= '</div>'; // ts-slides
$ts .= '</div>'; // ts-tv__screen
$ts .= '<div class="ts-tv__port"></div>';
$ts .= '</div>';
$ts .= '<div class="ts-tv__feet"><div class="ts-tv__foot"></div><div class="ts-tv__foot"></div></div>';
$ts .= '</div>';
$ts .= '<div class="ts-stick">';
$ts .= '<div class="ts-stick__body">';
$ts .= '<div class="ts-stick__led"></div>';
$ts .= '</div>';
$ts .= '<div class="ts-stick__connector"></div>';
$ts .= '</div>';
$ts .= '</div>';
$visual_html = $ts;
$visual_cls = 'platform-visual has-tv-stick';
} elseif ( ! empty( $a['neverGoesDark'] ) ) {
/* ── Never Goes Dark: player + TV + cloud connection/break animation ── */
$ngd = '<div class="ngd-stage" aria-hidden="true">';
/* TV */
$ngd .= '<div class="ngd-tv">';
$ngd .= '<div class="ngd-tv__body">';
$ngd .= '<div class="ngd-tv__screen">';
$ngd .= '<div class="ngd-menu">';
$ngd .= '<div class="ngd-menu__hd">';
$ngd .= '<div class="ngd-menu__logo"></div>';
$ngd .= '<div class="ngd-menu__ttl"></div>';
$ngd .= '</div>';
$ngd .= '<div class="ngd-menu__cols">';
$ngd .= '<div class="ngd-menu__col">';
$ngd .= '<div class="ngd-menu__cat"></div>';
$ngd .= '<div class="ngd-menu__row"><span class="ngd-menu__name"></span><span class="ngd-menu__price"></span></div>';
$ngd .= '<div class="ngd-menu__row ngd-menu__row--hl"><span class="ngd-menu__name"></span><span class="ngd-menu__price"></span></div>';
$ngd .= '<div class="ngd-menu__row"><span class="ngd-menu__name"></span><span class="ngd-menu__price"></span></div>';
$ngd .= '</div>';
$ngd .= '<div class="ngd-menu__col">';
$ngd .= '<div class="ngd-menu__cat"></div>';
$ngd .= '<div class="ngd-menu__row"><span class="ngd-menu__name"></span><span class="ngd-menu__price"></span></div>';
$ngd .= '<div class="ngd-menu__row"><span class="ngd-menu__name"></span><span class="ngd-menu__price"></span></div>';
$ngd .= '<div class="ngd-menu__row ngd-menu__row--hl"><span class="ngd-menu__name"></span><span class="ngd-menu__price"></span></div>';
$ngd .= '</div>';
$ngd .= '</div>'; /* cols */
$ngd .= '<div class="ngd-menu__ticker"><div class="ngd-menu__ticker-inner"></div></div>';
$ngd .= '</div>'; /* ngd-menu */
$ngd .= '</div>'; /* ngd-tv__screen */
$ngd .= '<div class="ngd-tv__port"></div>';
$ngd .= '</div>'; /* ngd-tv__body */
$ngd .= '<div class="ngd-tv__feet"><div class="ngd-tv__foot"></div><div class="ngd-tv__foot"></div></div>';
$ngd .= '</div>'; /* ngd-tv */
/* Player device (stick style, plugged into TV right-side HDMI port) */
$ngd .= '<div class="ngd-player">';
$ngd .= '<div class="ngd-player__connector"></div>';
$ngd .= '<div class="ngd-player__body">';
$ngd .= '<div class="ngd-player__led"></div>';
$ngd .= '</div>';
$ngd .= '<div class="ngd-player__check">';
$ngd .= '<svg viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">';
$ngd .= '<circle cx="11" cy="11" r="10" stroke="#4CAF50" stroke-width="1.5"/>';
$ngd .= '<polyline points="6,11 9.5,14.5 16,7.5" stroke="#4CAF50" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>';
$ngd .= '</svg>';
$ngd .= '</div>';
$ngd .= '</div>'; /* ngd-player */
/* Signal wrap: vertical line connecting player to cloud above */
$ngd .= '<div class="ngd-signal-wrap">';
$ngd .= '<div class="ngd-cloud">';
$ngd .= '<svg class="ngd-cloud__svg" viewBox="0 0 64 46" fill="none" xmlns="http://www.w3.org/2000/svg">';
$ngd .= '<path class="ngd-cloud__path" d="M50 38H12C6.5 38 2 33.5 2 28s4.5-10 10-10c.35 0 .68.02 1 .05C15.3 12.4 20.8 8 27.5 8 35.2 8 41.5 14.3 41.5 22c0 .73-.06 1.45-.16 2.15C45.2 24.95 48 28.1 48 32v6z" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>';
$ngd .= '</svg>';
$ngd .= '</div>';
$ngd .= '<div class="ngd-signal-line">';
$ngd .= '<div class="ngd-signal__dots">';
$ngd .= '<div class="ngd-signal__dot ngd-signal__dot--1"></div>';
$ngd .= '<div class="ngd-signal__dot ngd-signal__dot--2"></div>';
$ngd .= '<div class="ngd-signal__dot ngd-signal__dot--3"></div>';
$ngd .= '</div>';
$ngd .= '<div class="ngd-signal__break">';
$ngd .= '<svg viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">';
$ngd .= '<circle cx="9" cy="9" r="8" fill="rgba(239,68,68,0.12)" stroke="#ef4444" stroke-width="1.2"/>';
$ngd .= '<line x1="5.5" y1="5.5" x2="12.5" y2="12.5" stroke="#ef4444" stroke-width="2" stroke-linecap="round"/>';
$ngd .= '<line x1="12.5" y1="5.5" x2="5.5" y2="12.5" stroke="#ef4444" stroke-width="2" stroke-linecap="round"/>';
$ngd .= '</svg>';
$ngd .= '</div>';
$ngd .= '</div>'; /* ngd-signal-line */
$ngd .= '</div>'; /* ngd-signal-wrap */
$ngd .= '</div>'; /* ngd-stage */
$visual_html = $ngd;
$visual_cls = 'platform-visual has-ngd';
} elseif ( ! empty( $a['brandedAnim'] ) ) {
/* ── Custom Display Solutions: branded screens cascade animation ── */
$bd = '<div class="bd-stage" aria-hidden="true">';
/* ── Screen 1: Tablet (portrait kiosk) ── */
$bd .= '<div class="bd-device bd-device--tablet">';
$bd .= '<span class="bd-device__label">Kiosk</span>';
$bd .= '<div class="bd-device__body">';
$bd .= '<div class="bd-device__screen">';
$bd .= '<div class="bd-splash">';
$bd .= '<div class="bd-splash__logo"></div>';
$bd .= '</div>';
$bd .= '<div class="bd-ui">';
$bd .= '<div class="bd-ui__header"><div class="bd-ui__logo"></div><div class="bd-ui__brand-bar"></div></div>';
$bd .= '<div class="bd-ui__content">';
$bd .= '<div class="bd-promo bd-promo--welcome">';
$bd .= '<div class="bd-promo__hero"></div>';
$bd .= '<div class="bd-promo__heading"></div>';
$bd .= '<div class="bd-promo__text"></div>';
$bd .= '<div class="bd-promo__btn"></div>';
$bd .= '</div>';
$bd .= '</div>';
$bd .= '</div>';
$bd .= '</div>';
$bd .= '</div>';
$bd .= '</div>';
/* ── Screen 2: Wide wall-mount (landscape) ── */
$bd .= '<div class="bd-device bd-device--wall">';
$bd .= '<span class="bd-device__label">Wall Display</span>';
$bd .= '<div class="bd-device__body">';
$bd .= '<div class="bd-device__screen">';
$bd .= '<div class="bd-splash">';
$bd .= '<div class="bd-splash__logo"></div>';
$bd .= '</div>';
$bd .= '<div class="bd-ui">';
$bd .= '<div class="bd-ui__header"><div class="bd-ui__logo"></div><div class="bd-ui__brand-bar"></div></div>';
$bd .= '<div class="bd-ui__content">';
$bd .= '<div class="bd-promo bd-promo--sale">';
$bd .= '<div class="bd-promo__cols">';
$bd .= '<div class="bd-promo__visual"></div>';
$bd .= '<div class="bd-promo__info">';
$bd .= '<div class="bd-promo__badge"></div>';
$bd .= '<div class="bd-promo__heading"></div>';
$bd .= '<div class="bd-promo__text"></div>';
$bd .= '<div class="bd-promo__text bd-promo__text--short"></div>';
$bd .= '<div class="bd-promo__price"></div>';
$bd .= '</div>';
$bd .= '</div>';
$bd .= '</div>';
$bd .= '</div>';
$bd .= '</div>';
$bd .= '</div>';
$bd .= '</div>';
$bd .= '<div class="bd-mount"></div>';
$bd .= '</div>';
/* ── Screen 3: Interactive tablet on table ── */
$bd .= '<div class="bd-device bd-device--interactive">';
$bd .= '<span class="bd-device__label">Interactive</span>';
$bd .= '<div class="bd-device__body">';
$bd .= '<div class="bd-device__screen">';
$bd .= '<div class="bd-splash">';
$bd .= '<div class="bd-splash__logo"></div>';
$bd .= '</div>';
$bd .= '<div class="bd-ui">';
$bd .= '<div class="bd-ui__header"><div class="bd-ui__logo"></div><div class="bd-ui__brand-bar"></div></div>';
$bd .= '<div class="bd-ui__content">';
$bd .= '<div class="bd-promo bd-promo--menu">';
$bd .= '<div class="bd-promo__heading"></div>';
$bd .= '<div class="bd-promo__grid">';
$bd .= '<div class="bd-promo__tile"><div class="bd-promo__tile-img"></div><div class="bd-promo__tile-lbl"></div></div>';
$bd .= '<div class="bd-promo__tile"><div class="bd-promo__tile-img"></div><div class="bd-promo__tile-lbl"></div></div>';
$bd .= '<div class="bd-promo__tile"><div class="bd-promo__tile-img"></div><div class="bd-promo__tile-lbl"></div></div>';
$bd .= '<div class="bd-promo__tile"><div class="bd-promo__tile-img"></div><div class="bd-promo__tile-lbl"></div></div>';
$bd .= '</div>';
$bd .= '</div>';
$bd .= '</div>';
$bd .= '</div>';
$bd .= '</div>';
$bd .= '</div>';
$bd .= '<div class="bd-table"></div>';
$bd .= '</div>';
$bd .= '</div>'; /* bd-stage */
$visual_html = $bd;
$visual_cls = 'platform-visual has-branded';
} elseif ( ! empty( $a['cameraAnim'] ) ) {
$ca = '<div class="cam-stage" aria-hidden="true">';
// ── Left: compact photo camera ──
$ca .= '<div class="pc-wrap">';
$ca .= '<div class="pc-body">';
$ca .= '<div class="pc-flash-unit"></div>';
$ca .= '<div class="pc-top"><div class="pc-shutter-btn"></div><div class="pc-viewfinder"></div></div>';
$ca .= '<div class="pc-front">';
$ca .= '<div class="pc-lens-ring"><div class="pc-lens-glass"><div class="pc-lens-reflex"></div></div></div>';
$ca .= '<div class="pc-grip"></div>';
$ca .= '</div>';
$ca .= '</div>'; // pc-body
$ca .= '<div class="pc-prints">';
$ca .= '<div class="pc-print pc-print--1"><div class="pc-print__img"></div></div>';
$ca .= '<div class="pc-print pc-print--2"><div class="pc-print__img"></div></div>';
$ca .= '<div class="pc-print pc-print--3"><div class="pc-print__img"></div></div>';
$ca .= '</div>'; // pc-prints
$ca .= '</div>'; // pc-wrap
// ── Centre: product scene both cameras shoot ──
$ca .= '<div class="cam-scene">';
$ca .= '<div class="cam-subject cam-subject--1"></div>';
$ca .= '<div class="cam-subject cam-subject--2"></div>';
$ca .= '<div class="cam-subject cam-subject--3"></div>';
$ca .= '<div class="cam-flash-overlay"></div>';
$ca .= '<div class="cam-vid-overlay"></div>';
$ca .= '</div>'; // cam-scene
// ── Right: retro VHS video camera on tripod ──
$ca .= '<div class="vc-wrap">';
$ca .= '<div class="vc-camera">';
$ca .= '<div class="vc-handle"></div>';
$ca .= '<div class="vc-body">';
$ca .= '<div class="vc-lens-barrel"><div class="vc-lens-tip"><div class="vc-lens-glass"><div class="vc-lens-reflex"></div></div></div></div>';
$ca .= '<div class="vc-top-rail"></div>';
$ca .= '<div class="vc-rec-light"></div>';
$ca .= '<div class="vc-eyepiece"></div>';
$ca .= '</div>'; // vc-body
$ca .= '</div>'; // vc-camera
$ca .= '<div class="vc-tripod">';
$ca .= '<div class="vc-stem"></div>';
$ca .= '<div class="vc-legs">';
$ca .= '<div class="vc-leg vc-leg--l"></div>';
$ca .= '<div class="vc-leg vc-leg--c"></div>';
$ca .= '<div class="vc-leg vc-leg--r"></div>';
$ca .= '</div>'; // vc-legs
$ca .= '</div>'; // vc-tripod
$ca .= '</div>'; // vc-wrap
$ca .= '</div>'; // cam-stage
$visual_html = $ca;
$visual_cls = 'platform-visual has-camera';
/* ── Industry: Hospitality ─────────────────────────────── */
} elseif ( ! empty( $a['industryHospitality'] ) ) {
$visual_html = '<div class="ind-stage" data-industry-anim="hospitality" aria-hidden="true">'
. '<svg viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Hospitality signage environment">'
/* ── Background: restaurant counter area ── */
. '<rect x="0" y="0" width="400" height="300" rx="8" fill="#1a1a2e" />'
. '<rect x="0" y="240" width="400" height="60" rx="0" fill="#12121f" />'
/* ── Wall-mounted menu board (large display) ── */
. '<g transform="translate(20,20)">'
. '<rect x="0" y="0" width="220" height="150" rx="4" fill="#111" stroke="#333" stroke-width="1.5" />'
. '<rect x="6" y="6" width="208" height="138" rx="2" fill="#1c2333" />'
/* Menu header */
. '<rect x="14" y="14" width="80" height="8" rx="2" fill="#D83302" opacity=".8" />'
. '<text x="14" y="35" font-size="7" fill="rgba(255,255,255,.4)" font-family="Inter,sans-serif">TODAY\'S SPECIALS</text>'
/* Menu items with prices */
. '<text x="14" y="52" font-size="8" fill="rgba(255,255,255,.7)" font-family="Inter,sans-serif">Grilled Salmon</text>'
. '<text class="ind-menu-price" x="192" y="52" font-size="8" fill="#4CAF50" font-family="Inter,sans-serif" text-anchor="end">$18.50</text>'
. '<rect class="ind-menu-bar" x="14" y="55" width="40" height="3" rx="1" fill="#D83302" opacity=".4" />'
. '<text x="14" y="72" font-size="8" fill="rgba(255,255,255,.7)" font-family="Inter,sans-serif">Wagyu Burger</text>'
. '<text class="ind-menu-price" x="192" y="72" font-size="8" fill="#4CAF50" font-family="Inter,sans-serif" text-anchor="end">$22.00</text>'
. '<rect class="ind-menu-bar" x="14" y="75" width="55" height="3" rx="1" fill="#D83302" opacity=".4" />'
. '<text x="14" y="92" font-size="8" fill="rgba(255,255,255,.7)" font-family="Inter,sans-serif">Caesar Salad</text>'
. '<text class="ind-menu-price" x="192" y="92" font-size="8" fill="#4CAF50" font-family="Inter,sans-serif" text-anchor="end">$12.00</text>'
. '<rect class="ind-menu-bar" x="14" y="95" width="30" height="3" rx="1" fill="#D83302" opacity=".4" />'
. '<text x="14" y="112" font-size="8" fill="rgba(255,255,255,.7)" font-family="Inter,sans-serif">Pasta Carbonara</text>'
. '<text class="ind-menu-price" x="192" y="112" font-size="8" fill="#4CAF50" font-family="Inter,sans-serif" text-anchor="end">$15.00</text>'
. '<rect class="ind-menu-bar" x="14" y="115" width="45" height="3" rx="1" fill="#D83302" opacity=".4" />'
/* Promo banner */
. '<rect x="6" y="126" width="208" height="18" fill="#D83302" opacity=".15" />'
. '<text class="ind-promo-text" x="110" y="138" font-size="7" fill="#D83302" font-family="Inter,sans-serif" text-anchor="middle" font-weight="600">HAPPY HOUR 5-7PM</text>'
. '</g>'
/* ── Tablet on counter (POS integration) ── */
. '<g transform="translate(270,60)">'
. '<rect x="0" y="0" width="100" height="140" rx="6" fill="#222" stroke="#444" stroke-width="1" />'
. '<rect x="6" y="8" width="88" height="110" rx="2" fill="#1c2333" />'
. '<circle cx="50" cy="132" r="4" fill="#333" />'
/* POS mini-UI */
. '<rect x="12" y="16" width="76" height="6" rx="1" fill="#00757c" opacity=".6" />'
. '<text x="50" y="38" font-size="6" fill="rgba(255,255,255,.5)" font-family="Inter,sans-serif" text-anchor="middle">Order #47</text>'
. '<rect x="12" y="44" width="76" height="1" fill="rgba(255,255,255,.1)" />'
. '<text x="12" y="56" font-size="5.5" fill="rgba(255,255,255,.4)" font-family="Inter,sans-serif">1x Salmon</text>'
. '<text x="88" y="56" font-size="5.5" fill="rgba(255,255,255,.4)" font-family="Inter,sans-serif" text-anchor="end">$18.50</text>'
. '<text x="12" y="66" font-size="5.5" fill="rgba(255,255,255,.4)" font-family="Inter,sans-serif">2x Lager</text>'
. '<text x="88" y="66" font-size="5.5" fill="rgba(255,255,255,.4)" font-family="Inter,sans-serif" text-anchor="end">$14.00</text>'
. '<rect x="12" y="74" width="76" height="1" fill="rgba(255,255,255,.1)" />'
. '<text x="12" y="86" font-size="6" fill="rgba(255,255,255,.7)" font-family="Inter,sans-serif" font-weight="600">Total</text>'
. '<text x="88" y="86" font-size="6" fill="#4CAF50" font-family="Inter,sans-serif" text-anchor="end" font-weight="600">$32.50</text>'
. '<rect x="12" y="96" width="76" height="16" rx="3" fill="#4CAF50" opacity=".8" />'
. '<text x="50" y="107" font-size="6" fill="#fff" font-family="Inter,sans-serif" text-anchor="middle" font-weight="600">PAY</text>'
. '</g>'
/* ── Ambient: counter top ── */
. '<rect x="0" y="235" width="400" height="3" fill="#333" />'
/* ── Label: wall screen type ── */
. '<text x="130" y="185" font-size="7" fill="rgba(255,255,255,.25)" font-family="Inter,sans-serif" text-anchor="middle" font-weight="600" letter-spacing=".1em">MENU BOARD</text>'
. '<text x="320" y="215" font-size="7" fill="rgba(255,255,255,.25)" font-family="Inter,sans-serif" text-anchor="middle" font-weight="600" letter-spacing=".1em">POS TABLET</text>'
. '</svg></div>';
$visual_cls = 'platform-visual has-industry';
/* ── Industry: Retail ──────────────────────────────────── */
} elseif ( ! empty( $a['industryRetail'] ) ) {
$visual_html = '<div class="ind-stage" data-industry-anim="retail" aria-hidden="true">'
. '<svg viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Retail signage environment">'
. '<rect x="0" y="0" width="400" height="300" rx="8" fill="#1a1a2e" />'
/* ── Large overhead promo display ── */
. '<g transform="translate(20,15)">'
. '<rect x="0" y="0" width="240" height="130" rx="4" fill="#111" stroke="#333" stroke-width="1.5" />'
. '<rect x="5" y="5" width="230" height="120" rx="2" fill="#1c2333" />'
/* Promo content */
. '<rect x="12" y="12" width="60" height="8" rx="2" fill="#D83302" opacity=".8" />'
. '<text x="12" y="36" font-size="11" fill="rgba(255,255,255,.9)" font-family="Inter,sans-serif" font-weight="700">SPRING SALE</text>'
. '<text x="12" y="50" font-size="7" fill="rgba(255,255,255,.5)" font-family="Inter,sans-serif">Up to 40% off selected items</text>'
/* Product grid */
. '<g transform="translate(12,60)">'
. '<rect class="ind-product-slot" x="0" y="0" width="48" height="48" rx="3" fill="#252540" stroke="#D83302" stroke-width="0" />'
. '<rect x="6" y="6" width="36" height="24" rx="2" fill="#333" />'
. '<text class="ind-sale-tag" x="24" y="42" font-size="6" fill="#4CAF50" font-family="Inter,sans-serif" text-anchor="middle">$29.99</text>'
. '<rect class="ind-product-slot" x="56" y="0" width="48" height="48" rx="3" fill="#252540" stroke="#D83302" stroke-width="0" />'
. '<rect x="62" y="6" width="36" height="24" rx="2" fill="#333" />'
. '<text class="ind-sale-tag" x="80" y="42" font-size="6" fill="#4CAF50" font-family="Inter,sans-serif" text-anchor="middle">$49.99</text>'
. '<rect class="ind-product-slot" x="112" y="0" width="48" height="48" rx="3" fill="#252540" stroke="#D83302" stroke-width="0" />'
. '<rect x="118" y="6" width="36" height="24" rx="2" fill="#333" />'
. '<text class="ind-sale-tag" x="136" y="42" font-size="6" fill="#4CAF50" font-family="Inter,sans-serif" text-anchor="middle">$19.99</text>'
. '<rect class="ind-product-slot" x="168" y="0" width="48" height="48" rx="3" fill="#252540" stroke="#D83302" stroke-width="0" />'
. '<rect x="174" y="6" width="36" height="24" rx="2" fill="#333" />'
. '<text class="ind-sale-tag" x="192" y="42" font-size="6" fill="#4CAF50" font-family="Inter,sans-serif" text-anchor="middle">$39.99</text>'
. '</g>'
. '</g>'
/* ── Analytics monitor (small) ── */
. '<g transform="translate(280,15)">'
. '<rect x="0" y="0" width="100" height="80" rx="3" fill="#111" stroke="#333" stroke-width="1" />'
. '<rect x="4" y="4" width="92" height="72" rx="2" fill="#1c2333" />'
. '<text x="10" y="16" font-size="6" fill="rgba(255,255,255,.5)" font-family="Inter,sans-serif" font-weight="600">FOOTFALL</text>'
. '<text class="ind-footfall-val" x="10" y="32" font-size="14" fill="#D83302" font-family="Inter,sans-serif" font-weight="700">243</text>'
. '<text x="10" y="42" font-size="5" fill="rgba(255,255,255,.3)" font-family="Inter,sans-serif">visitors today</text>'
/* Revenue mini bars */
. '<g transform="translate(10,48)">'
. '<rect class="ind-rev-bar" x="0" y="35" width="10" height="5" fill="#4CAF50" />'
. '<rect class="ind-rev-bar" x="14" y="30" width="10" height="10" fill="#4CAF50" />'
. '<rect class="ind-rev-bar" x="28" y="25" width="10" height="15" fill="#4CAF50" />'
. '<rect class="ind-rev-bar" x="42" y="20" width="10" height="20" fill="#D83302" />'
. '<rect class="ind-rev-bar" x="56" y="28" width="10" height="12" fill="#4CAF50" />'
. '<rect class="ind-rev-bar" x="70" y="22" width="10" height="18" fill="#4CAF50" />'
. '</g>'
. '</g>'
/* ── Wayfinding pillar (floor standing) ── */
. '<g transform="translate(290,110)">'
. '<rect x="10" y="0" width="80" height="120" rx="3" fill="#111" stroke="#333" stroke-width="1" />'
. '<rect x="14" y="4" width="72" height="112" rx="2" fill="#1c2333" />'
. '<text x="50" y="18" font-size="6" fill="rgba(255,255,255,.5)" font-family="Inter,sans-serif" text-anchor="middle" font-weight="600">FLOOR GUIDE</text>'
. '<text x="22" y="34" font-size="6" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">\u2190 Menswear</text>'
. '<text x="22" y="48" font-size="6" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">\u2192 Electronics</text>'
. '<text x="22" y="62" font-size="6" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">\u2191 Food Court</text>'
. '<text x="22" y="76" font-size="6" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">\u2190 Home &amp; Garden</text>'
. '<rect x="30" y="86" width="40" height="22" rx="3" fill="#D83302" opacity=".2" />'
. '<text x="50" y="100" font-size="6" fill="#D83302" font-family="Inter,sans-serif" text-anchor="middle" font-weight="600">MAP</text>'
. '</g>'
/* ── Shelf edge (store floor) ── */
. '<rect x="0" y="248" width="270" height="52" fill="#12121f" />'
. '<rect x="20" y="250" width="230" height="4" fill="#333" />'
. '<text x="135" y="270" font-size="7" fill="rgba(255,255,255,.25)" font-family="Inter,sans-serif" text-anchor="middle" font-weight="600" letter-spacing=".1em">PROMO DISPLAY</text>'
. '<text x="340" y="245" font-size="7" fill="rgba(255,255,255,.25)" font-family="Inter,sans-serif" text-anchor="middle" font-weight="600" letter-spacing=".1em">WAYFINDING</text>'
. '</svg></div>';
$visual_cls = 'platform-visual has-industry';
/* ── Industry: Corporate Office ─────────────────────────── */
} elseif ( ! empty( $a['industryCorporate'] ) ) {
$visual_html = '<div class="ind-stage" data-industry-anim="corporate" aria-hidden="true">'
. '<svg viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Corporate office signage environment">'
. '<rect x="0" y="0" width="400" height="300" rx="8" fill="#1a1a2e" />'
/* ── Meeting room display (wall-mounted) ── */
. '<g transform="translate(20,20)">'
. '<rect x="0" y="0" width="170" height="120" rx="4" fill="#111" stroke="#333" stroke-width="1.5" />'
. '<rect x="5" y="5" width="160" height="110" rx="2" fill="#1c2333" />'
/* Teams-style header */
. '<rect x="10" y="10" width="150" height="14" rx="2" fill="#5b5fc7" opacity=".3" />'
. '<text x="18" y="20" font-size="6" fill="rgba(255,255,255,.8)" font-family="Inter,sans-serif" font-weight="600">\uD83D\uDCF9 Room: Boardroom A</text>'
/* Status */
. '<circle class="ind-meet-dot" cx="18" cy="38" r="4" fill="#4CAF50" />'
. '<text class="ind-meet-status" x="28" y="41" font-size="7" fill="rgba(255,255,255,.7)" font-family="Inter,sans-serif">Available</text>'
/* Schedule */
. '<text x="10" y="58" font-size="5.5" fill="rgba(255,255,255,.4)" font-family="Inter,sans-serif">UPCOMING</text>'
. '<text x="10" y="70" font-size="6" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">10:00 - Sprint Planning</text>'
. '<text x="10" y="82" font-size="6" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">13:00 - Design Review</text>'
. '<text x="10" y="94" font-size="6" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">15:30 - All Hands</text>'
. '<rect x="10" y="100" width="150" height="10" rx="2" fill="#4CAF50" opacity=".15" />'
. '<text x="85" y="108" font-size="5.5" fill="#4CAF50" font-family="Inter,sans-serif" text-anchor="middle">Book Now</text>'
. '</g>'
/* ── KPI Dashboard (large monitor on stand) ── */
. '<g transform="translate(210,10)">'
. '<rect x="0" y="0" width="170" height="130" rx="4" fill="#111" stroke="#333" stroke-width="1.5" />'
. '<rect x="5" y="5" width="160" height="120" rx="2" fill="#1c2333" />'
. '<text x="12" y="18" font-size="6" fill="rgba(255,255,255,.5)" font-family="Inter,sans-serif" font-weight="600">LIVE DASHBOARD</text>'
/* KPI cards */
. '<g transform="translate(12,26)">'
. '<rect x="0" y="0" width="44" height="32" rx="2" fill="#252540" />'
. '<text x="22" y="11" font-size="5" fill="rgba(255,255,255,.4)" font-family="Inter,sans-serif" text-anchor="middle">Uptime</text>'
. '<text class="ind-kpi-val" x="22" y="24" font-size="10" fill="#4CAF50" font-family="Inter,sans-serif" text-anchor="middle" font-weight="700">99%</text>'
. '</g>'
. '<g transform="translate(62,26)">'
. '<rect x="0" y="0" width="44" height="32" rx="2" fill="#252540" />'
. '<text x="22" y="11" font-size="5" fill="rgba(255,255,255,.4)" font-family="Inter,sans-serif" text-anchor="middle">Users</text>'
. '<text class="ind-kpi-val" x="22" y="24" font-size="10" fill="#D83302" font-family="Inter,sans-serif" text-anchor="middle" font-weight="700">240</text>'
. '</g>'
. '<g transform="translate(112,26)">'
. '<rect x="0" y="0" width="44" height="32" rx="2" fill="#252540" />'
. '<text x="22" y="11" font-size="5" fill="rgba(255,255,255,.4)" font-family="Inter,sans-serif" text-anchor="middle">Latency</text>'
. '<text class="ind-kpi-val" x="22" y="24" font-size="10" fill="#f59e0b" font-family="Inter,sans-serif" text-anchor="middle" font-weight="700">22ms</text>'
. '</g>'
/* Trend line */
. '<g transform="translate(12,66)">'
. '<text x="0" y="0" font-size="5" fill="rgba(255,255,255,.4)" font-family="Inter,sans-serif">REQUESTS / MIN</text>'
. '<line x1="0" y1="10" x2="140" y2="10" stroke="rgba(255,255,255,.06)" stroke-width=".5" />'
. '<line x1="0" y1="25" x2="140" y2="25" stroke="rgba(255,255,255,.06)" stroke-width=".5" />'
. '<line x1="0" y1="40" x2="140" y2="40" stroke="rgba(255,255,255,.06)" stroke-width=".5" />'
. '<path class="ind-corp-line" d="M0,25 L140,25" stroke="#4CAF50" stroke-width="1.5" fill="none" stroke-linecap="round" />'
. '</g>'
/* Monitor stand */
. '<rect x="75" y="130" width="20" height="12" fill="#222" />'
. '<rect x="55" y="140" width="60" height="4" rx="2" fill="#333" />'
. '</g>'
/* ── Announcement ticker (lobby display) ── */
. '<g transform="translate(20,160)">'
. '<rect x="0" y="0" width="360" height="40" rx="4" fill="#111" stroke="#333" stroke-width="1" />'
. '<rect x="4" y="4" width="352" height="32" rx="2" fill="#0d1117" />'
. '<rect x="8" y="8" width="60" height="12" rx="2" fill="#D83302" opacity=".2" />'
. '<text x="38" y="17" font-size="6" fill="#D83302" font-family="Inter,sans-serif" text-anchor="middle" font-weight="600">COMPANY NEWS</text>'
. '<text x="80" y="30" font-size="6.5" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">Q4 Results exceed forecast \u2022 New office opening in March \u2022 Hackathon registrations open</text>'
. '</g>'
/* Labels */
. '<text x="105" y="220" font-size="7" fill="rgba(255,255,255,.25)" font-family="Inter,sans-serif" text-anchor="middle" font-weight="600" letter-spacing=".1em">MEETING ROOM</text>'
. '<text x="295" y="220" font-size="7" fill="rgba(255,255,255,.25)" font-family="Inter,sans-serif" text-anchor="middle" font-weight="600" letter-spacing=".1em">KPI DASHBOARD</text>'
. '</svg></div>';
$visual_cls = 'platform-visual has-industry';
/* ── Industry: Education ───────────────────────────────── */
} elseif ( ! empty( $a['industryEducation'] ) ) {
$visual_html = '<div class="ind-stage" data-industry-anim="education" aria-hidden="true">'
. '<svg viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Education signage environment">'
. '<rect x="0" y="0" width="400" height="300" rx="8" fill="#1a1a2e" />'
/* ── Timetable display (large wall-mount) ── */
. '<g transform="translate(15,15)">'
. '<rect x="0" y="0" width="230" height="160" rx="4" fill="#111" stroke="#333" stroke-width="1.5" />'
. '<rect x="5" y="5" width="220" height="150" rx="2" fill="#1c2333" />'
. '<text x="12" y="18" font-size="7" fill="rgba(255,255,255,.5)" font-family="Inter,sans-serif" font-weight="600">DAILY TIMETABLE</text>'
. '<text x="200" y="18" font-size="6" fill="rgba(255,255,255,.3)" font-family="Inter,sans-serif" text-anchor="end">Tuesday</text>'
/* Column headers */
. '<g transform="translate(12,28)">'
. '<rect x="0" y="0" width="200" height="10" rx="1" fill="rgba(255,255,255,.05)" />'
. '<text x="6" y="7" font-size="5" fill="rgba(255,255,255,.4)" font-family="Inter,sans-serif">TIME</text>'
. '<text x="50" y="7" font-size="5" fill="rgba(255,255,255,.4)" font-family="Inter,sans-serif">ROOM</text>'
. '<text x="100" y="7" font-size="5" fill="rgba(255,255,255,.4)" font-family="Inter,sans-serif">SUBJECT</text>'
. '<text x="170" y="7" font-size="5" fill="rgba(255,255,255,.4)" font-family="Inter,sans-serif">STATUS</text>'
. '</g>'
/* Schedule rows */
. '<g transform="translate(12,44)">'
. '<rect class="ind-sched-slot" x="0" y="0" width="200" height="18" rx="2" fill="#252540" />'
. '<text x="6" y="12" font-size="6" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">09:00</text>'
. '<text x="50" y="12" font-size="6" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">B201</text>'
. '<text x="100" y="12" font-size="6" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">Mathematics</text>'
. '<circle cx="185" cy="9" r="3" fill="#4CAF50" />'
. '</g>'
. '<g transform="translate(12,66)">'
. '<rect class="ind-sched-slot" x="0" y="0" width="200" height="18" rx="2" fill="#252540" />'
. '<text x="6" y="12" font-size="6" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">10:30</text>'
. '<text x="50" y="12" font-size="6" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">Lab 3</text>'
. '<text x="100" y="12" font-size="6" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">Chemistry</text>'
. '<circle cx="185" cy="9" r="3" fill="#f59e0b" />'
. '</g>'
. '<g transform="translate(12,88)">'
. '<rect class="ind-sched-slot" x="0" y="0" width="200" height="18" rx="2" fill="#252540" />'
. '<text x="6" y="12" font-size="6" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">13:00</text>'
. '<text x="50" y="12" font-size="6" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">Hall A</text>'
. '<text x="100" y="12" font-size="6" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">Literature</text>'
. '<circle cx="185" cy="9" r="3" fill="#4CAF50" />'
. '</g>'
. '<g transform="translate(12,110)">'
. '<rect class="ind-sched-slot" x="0" y="0" width="200" height="18" rx="2" fill="#252540" />'
. '<text x="6" y="12" font-size="6" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">14:30</text>'
. '<text x="50" y="12" font-size="6" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">Gym</text>'
. '<text x="100" y="12" font-size="6" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">PE</text>'
. '<circle cx="185" cy="9" r="3" fill="#4CAF50" />'
. '</g>'
. '<g transform="translate(12,132)">'
. '<rect class="ind-sched-slot" x="0" y="0" width="200" height="18" rx="2" fill="#252540" />'
. '<text x="6" y="12" font-size="6" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">16:00</text>'
. '<text x="50" y="12" font-size="6" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">C105</text>'
. '<text x="100" y="12" font-size="6" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">Art</text>'
. '<circle cx="185" cy="9" r="3" fill="#4CAF50" />'
. '</g>'
. '</g>'
/* ── Campus wayfinding (floor directory) ── */
. '<g transform="translate(260,15)">'
. '<rect x="0" y="0" width="120" height="100" rx="3" fill="#111" stroke="#333" stroke-width="1" />'
. '<rect x="4" y="4" width="112" height="92" rx="2" fill="#1c2333" />'
. '<text x="60" y="16" font-size="6" fill="rgba(255,255,255,.5)" font-family="Inter,sans-serif" text-anchor="middle" font-weight="600">CAMPUS MAP</text>'
/* Simplified building outlines with wayfinding dots */
. '<rect x="15" y="24" width="30" height="20" rx="2" fill="#252540" stroke="rgba(255,255,255,.1)" stroke-width=".5" />'
. '<rect x="55" y="28" width="25" height="16" rx="2" fill="#252540" stroke="rgba(255,255,255,.1)" stroke-width=".5" />'
. '<rect x="90" y="22" width="18" height="24" rx="2" fill="#252540" stroke="rgba(255,255,255,.1)" stroke-width=".5" />'
. '<rect x="30" y="54" width="45" height="18" rx="2" fill="#252540" stroke="rgba(255,255,255,.1)" stroke-width=".5" />'
. '<circle class="ind-wf-dot" cx="30" cy="34" r="3" fill="#D83302" />'
. '<circle class="ind-wf-dot" cx="67" cy="36" r="3" fill="#D83302" opacity=".2" />'
. '<circle class="ind-wf-dot" cx="99" cy="34" r="3" fill="#D83302" opacity=".2" />'
. '<circle class="ind-wf-dot" cx="52" cy="63" r="3" fill="#D83302" opacity=".2" />'
. '<text x="60" y="86" font-size="5" fill="rgba(255,255,255,.3)" font-family="Inter,sans-serif" text-anchor="middle">You are here \u2022</text>'
. '</g>'
/* ── Emergency alert banner ── */
. '<g transform="translate(260,130)">'
. '<rect class="ind-alert-bar" x="0" y="0" width="120" height="30" rx="3" fill="#ef4444" opacity=".7" />'
. '<text x="10" y="12" font-size="5" fill="#fff" font-family="Inter,sans-serif" font-weight="700">\u26A0 ALERT</text>'
. '<text class="ind-alert-text" x="10" y="23" font-size="5.5" fill="rgba(255,255,255,.9)" font-family="Inter,sans-serif">Fire Drill 2:00 PM</text>'
. '</g>'
/* Labels */
. '<text x="130" y="195" font-size="7" fill="rgba(255,255,255,.25)" font-family="Inter,sans-serif" text-anchor="middle" font-weight="600" letter-spacing=".1em">TIMETABLE</text>'
. '<text x="320" y="180" font-size="7" fill="rgba(255,255,255,.25)" font-family="Inter,sans-serif" text-anchor="middle" font-weight="600" letter-spacing=".1em">WAYFINDING</text>'
. '</svg></div>';
$visual_cls = 'platform-visual has-industry';
/* ── Industry: Outdoor Marketplace ─────────────────────── */
} elseif ( ! empty( $a['industryOutdoor'] ) ) {
$visual_html = '<div class="ind-stage" data-industry-anim="outdoor" aria-hidden="true">'
. '<svg viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Outdoor marketplace signage environment">'
. '<rect x="0" y="0" width="400" height="300" rx="8" fill="#1a1a2e" />'
/* ── Market stall canopy ── */
. '<path d="M30,100 L200,80 L370,100 L370,110 L30,110 Z" fill="#252540" />'
. '<path d="M30,100 L200,80 L370,100" fill="none" stroke="#D83302" stroke-width="1.5" opacity=".4" />'
/* ── Overhead digital sign (weather resistant) ── */
. '<g transform="translate(60,20)">'
. '<rect x="0" y="0" width="280" height="55" rx="4" fill="#111" stroke="#00757c" stroke-width="1.5" />'
. '<rect x="4" y="4" width="272" height="47" rx="2" fill="#1c2333" />'
/* Weather widget */
. '<text class="ind-weather-icon" x="22" y="32" font-size="20" text-anchor="middle">\u2600</text>'
. '<text class="ind-weather-temp" x="22" y="46" font-size="7" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif" text-anchor="middle">24\u00B0C</text>'
/* Market info */
. '<text x="55" y="18" font-size="6" fill="rgba(255,255,255,.4)" font-family="Inter,sans-serif" font-weight="600">GREENFIELD MARKET</text>'
. '<text x="55" y="30" font-size="8" fill="rgba(255,255,255,.8)" font-family="Inter,sans-serif" font-weight="700">Fresh Produce \u2022 Artisan Goods</text>'
. '<text x="55" y="42" font-size="6" fill="rgba(255,255,255,.4)" font-family="Inter,sans-serif">Open Today 8AM \u2013 4PM</text>'
/* Busy indicator */
. '<circle class="ind-busy-dot" cx="252" cy="25" r="5" fill="#4CAF50" />'
. '<text class="ind-busy-label" x="252" y="40" font-size="5" fill="rgba(255,255,255,.5)" font-family="Inter,sans-serif" text-anchor="middle">Quiet</text>'
. '</g>'
/* ── Vendor tablet (on counter) ── */
. '<g transform="translate(40,120)">'
. '<rect x="0" y="0" width="140" height="100" rx="4" fill="#111" stroke="#333" stroke-width="1" />'
. '<rect x="4" y="4" width="132" height="92" rx="2" fill="#1c2333" />'
. '<text x="10" y="16" font-size="6" fill="rgba(255,255,255,.5)" font-family="Inter,sans-serif" font-weight="600">VENDOR: FARM FRESH</text>'
. '<rect x="10" y="22" width="120" height="1" fill="rgba(255,255,255,.1)" />'
/* Product categories */
. '<text x="10" y="36" font-size="6" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">Tomatoes</text>'
. '<text x="110" y="36" font-size="6" fill="#4CAF50" font-family="Inter,sans-serif" text-anchor="end">$3/kg</text>'
. '<text x="10" y="48" font-size="6" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">Strawberries</text>'
. '<text x="110" y="48" font-size="6" fill="#4CAF50" font-family="Inter,sans-serif" text-anchor="end">$5/punnet</text>'
. '<text x="10" y="60" font-size="6" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">Sourdough</text>'
. '<text x="110" y="60" font-size="6" fill="#4CAF50" font-family="Inter,sans-serif" text-anchor="end">$8/loaf</text>'
/* Activity mini chart */
. '<text x="10" y="76" font-size="5" fill="rgba(255,255,255,.3)" font-family="Inter,sans-serif" font-weight="600">SALES TODAY</text>'
. '<g transform="translate(10,80)">'
. '<rect class="ind-vendor-bar" x="0" y="25" width="12" height="3" fill="#D83302" />'
. '<rect class="ind-vendor-bar" x="16" y="20" width="12" height="8" fill="#D83302" />'
. '<rect class="ind-vendor-bar" x="32" y="15" width="12" height="13" fill="#D83302" />'
. '<rect class="ind-vendor-bar" x="48" y="10" width="12" height="18" fill="#4CAF50" />'
. '<rect class="ind-vendor-bar" x="64" y="18" width="12" height="10" fill="#D83302" />'
. '<rect class="ind-vendor-bar" x="80" y="12" width="12" height="16" fill="#D83302" />'
. '<rect class="ind-vendor-bar" x="96" y="8" width="12" height="20" fill="#4CAF50" />'
. '</g>'
. '</g>'
/* ── Second display event board (standing) ── */
. '<g transform="translate(220,120)">'
. '<rect x="0" y="0" width="140" height="100" rx="3" fill="#111" stroke="#333" stroke-width="1" />'
. '<rect x="4" y="4" width="132" height="92" rx="2" fill="#1c2333" />'
. '<text x="70" y="16" font-size="6" fill="rgba(255,255,255,.5)" font-family="Inter,sans-serif" text-anchor="middle" font-weight="600">UPCOMING EVENTS</text>'
. '<rect x="10" y="22" width="120" height="1" fill="rgba(255,255,255,.1)" />'
. '<text x="10" y="36" font-size="6" fill="#D83302" font-family="Inter,sans-serif">\u2022 Live Music 12:00</text>'
. '<text x="10" y="48" font-size="6" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">\u2022 Cooking Demo 1:30</text>'
. '<text x="10" y="60" font-size="6" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">\u2022 Kids Workshop 2:00</text>'
. '<text x="10" y="72" font-size="6" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">\u2022 Prize Draw 3:30</text>'
. '<rect x="10" y="78" width="120" height="12" rx="2" fill="#00757c" opacity=".2" />'
. '<text x="70" y="87" font-size="5.5" fill="#00757c" font-family="Inter,sans-serif" text-anchor="middle" font-weight="600">VIEW FULL SCHEDULE</text>'
. '</g>'
/* Ground/grass hint */
. '<rect x="0" y="250" width="400" height="50" fill="#12121f" />'
. '<text x="110" y="245" font-size="7" fill="rgba(255,255,255,.25)" font-family="Inter,sans-serif" text-anchor="middle" font-weight="600" letter-spacing=".1em">VENDOR DISPLAY</text>'
. '<text x="290" y="245" font-size="7" fill="rgba(255,255,255,.25)" font-family="Inter,sans-serif" text-anchor="middle" font-weight="600" letter-spacing=".1em">EVENT BOARD</text>'
. '</svg></div>';
$visual_cls = 'platform-visual has-industry';
/* ── Industry: Live Data Displays ──────────────────────── */
} elseif ( ! empty( $a['industryLiveData'] ) ) {
$visual_html = '<div class="ind-stage" data-industry-anim="livedata" aria-hidden="true">'
. '<svg viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Live data display environment">'
. '<defs>'
. '<linearGradient id="indLdGrad" x1="0%" y1="0%" x2="0%" y2="100%">'
. '<stop offset="0%" stop-color="#D83302" stop-opacity="1"/>'
. '<stop offset="100%" stop-color="#4CAF50" stop-opacity=".8"/>'
. '</linearGradient>'
. '</defs>'
. '<rect x="0" y="0" width="400" height="300" rx="8" fill="#1a1a2e" />'
/* ── Monitor 1: Bar chart (left) ── */
. '<g transform="translate(10,15)">'
. '<rect x="0" y="0" width="125" height="130" rx="4" fill="#111" stroke="#333" stroke-width="1.5" />'
. '<rect x="4" y="4" width="117" height="122" rx="2" fill="#1c2333" />'
. '<text x="10" y="16" font-size="5.5" fill="rgba(255,255,255,.5)" font-family="Inter,sans-serif" font-weight="600">THROUGHPUT</text>'
. '<g transform="translate(10,22)">'
. '<rect class="ind-ld-bar" x="0" y="30" width="12" height="5" fill="url(#indLdGrad)" />'
. '<rect class="ind-ld-bar" x="16" y="25" width="12" height="10" fill="url(#indLdGrad)" />'
. '<rect class="ind-ld-bar" x="32" y="20" width="12" height="15" fill="url(#indLdGrad)" />'
. '<rect class="ind-ld-bar" x="48" y="15" width="12" height="20" fill="url(#indLdGrad)" />'
. '<rect class="ind-ld-bar" x="64" y="22" width="12" height="13" fill="url(#indLdGrad)" />'
. '<rect class="ind-ld-bar" x="80" y="18" width="12" height="17" fill="url(#indLdGrad)" />'
. '<rect class="ind-ld-bar" x="96" y="12" width="12" height="23" fill="url(#indLdGrad)" />'
. '</g>'
. '<g transform="translate(10,62)">'
. '<text class="ind-ld-val" x="6" y="0" font-size="5" fill="rgba(255,255,255,.4)" font-family="Inter,sans-serif" text-anchor="middle">0</text>'
. '<text class="ind-ld-val" x="22" y="0" font-size="5" fill="rgba(255,255,255,.4)" font-family="Inter,sans-serif" text-anchor="middle">0</text>'
. '<text class="ind-ld-val" x="38" y="0" font-size="5" fill="rgba(255,255,255,.4)" font-family="Inter,sans-serif" text-anchor="middle">0</text>'
. '<text class="ind-ld-val" x="54" y="0" font-size="5" fill="rgba(255,255,255,.4)" font-family="Inter,sans-serif" text-anchor="middle">0</text>'
. '<text class="ind-ld-val" x="70" y="0" font-size="5" fill="rgba(255,255,255,.4)" font-family="Inter,sans-serif" text-anchor="middle">0</text>'
. '<text class="ind-ld-val" x="86" y="0" font-size="5" fill="rgba(255,255,255,.4)" font-family="Inter,sans-serif" text-anchor="middle">0</text>'
. '<text class="ind-ld-val" x="102" y="0" font-size="5" fill="rgba(255,255,255,.4)" font-family="Inter,sans-serif" text-anchor="middle">0</text>'
. '</g>'
/* Status line */
. '<g transform="translate(10,76)">'
. '<circle class="ind-ld-alert" cx="4" cy="4" r="3" fill="#4CAF50" />'
. '<text class="ind-ld-alert-text" x="12" y="7" font-size="5.5" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">All Systems OK</text>'
. '</g>'
/* Line chart below */
. '<g transform="translate(10,90)">'
. '<text x="0" y="0" font-size="5" fill="rgba(255,255,255,.3)" font-family="Inter,sans-serif">LATENCY</text>'
. '<line x1="0" y1="8" x2="100" y2="8" stroke="rgba(255,255,255,.05)" stroke-width=".5" />'
. '<line x1="0" y1="20" x2="100" y2="20" stroke="rgba(255,255,255,.05)" stroke-width=".5" />'
. '<line x1="0" y1="32" x2="100" y2="32" stroke="rgba(255,255,255,.05)" stroke-width=".5" />'
. '<path class="ind-ld-line" d="M0,20 L110,20" stroke="#4CAF50" stroke-width="1.5" fill="none" stroke-linecap="round" />'
. '</g>'
/* Monitor stand */
. '<rect x="50" y="130" width="25" height="10" fill="#222" />'
. '<rect x="35" y="138" width="55" height="3" rx="1" fill="#333" />'
. '</g>'
/* ── Monitor 2: Pie chart + KPIs (center) ── */
. '<g transform="translate(145,15)">'
. '<rect x="0" y="0" width="110" height="130" rx="4" fill="#111" stroke="#333" stroke-width="1.5" />'
. '<rect x="4" y="4" width="102" height="122" rx="2" fill="#1c2333" />'
. '<text x="10" y="16" font-size="5.5" fill="rgba(255,255,255,.5)" font-family="Inter,sans-serif" font-weight="600">DISTRIBUTION</text>'
/* Pie chart */
. '<g transform="translate(55,56)">'
. '<path class="ind-ld-pie" d="M0,0 L0,-22 A22,22 0 0,1 15.56,-15.56 Z" fill="#D83302" opacity=".9"/>'
. '<path class="ind-ld-pie" d="M0,0 L15.56,-15.56 A22,22 0 0,1 22,0 Z" fill="#4CAF50" opacity=".8"/>'
. '<path class="ind-ld-pie" d="M0,0 L22,0 A22,22 0 0,1 0,22 Z" fill="#f59e0b" opacity=".7"/>'
. '<path class="ind-ld-pie" d="M0,0 L0,22 A22,22 0 0,1 -22,0 Z" fill="#5b5fc7" opacity=".7"/>'
. '<path class="ind-ld-pie" d="M0,0 L-22,0 A22,22 0 0,1 0,-22 Z" fill="#ef4444" opacity=".6"/>'
. '<circle cx="0" cy="0" r="10" fill="#1c2333" stroke="rgba(255,255,255,.1)" stroke-width=".5"/>'
. '</g>'
/* Legend */
. '<g transform="translate(10,88)">'
. '<rect x="0" y="0" width="5" height="5" fill="#D83302"/><text x="8" y="5" font-size="4.5" fill="rgba(255,255,255,.4)" font-family="Inter,sans-serif">API</text>'
. '<rect x="30" y="0" width="5" height="5" fill="#4CAF50"/><text x="38" y="5" font-size="4.5" fill="rgba(255,255,255,.4)" font-family="Inter,sans-serif">DB</text>'
. '<rect x="58" y="0" width="5" height="5" fill="#f59e0b"/><text x="66" y="5" font-size="4.5" fill="rgba(255,255,255,.4)" font-family="Inter,sans-serif">Cache</text>'
. '<rect x="0" y="12" width="5" height="5" fill="#5b5fc7"/><text x="8" y="17" font-size="4.5" fill="rgba(255,255,255,.4)" font-family="Inter,sans-serif">Queue</text>'
. '<rect x="40" y="12" width="5" height="5" fill="#ef4444"/><text x="48" y="17" font-size="4.5" fill="rgba(255,255,255,.4)" font-family="Inter,sans-serif">Worker</text>'
. '</g>'
/* Monitor stand */
. '<rect x="40" y="130" width="30" height="10" fill="#222" />'
. '<rect x="25" y="138" width="60" height="3" rx="1" fill="#333" />'
. '</g>'
/* ── Monitor 3: Alert feed (right) ── */
. '<g transform="translate(265,15)">'
. '<rect x="0" y="0" width="125" height="130" rx="4" fill="#111" stroke="#333" stroke-width="1.5" />'
. '<rect x="4" y="4" width="117" height="122" rx="2" fill="#1c2333" />'
. '<text x="10" y="16" font-size="5.5" fill="rgba(255,255,255,.5)" font-family="Inter,sans-serif" font-weight="600">SYSTEM STATUS</text>'
/* Status rows */
. '<g transform="translate(10,24)">'
. '<circle cx="5" cy="4" r="3" fill="#4CAF50"/>'
. '<text x="12" y="7" font-size="5.5" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">API Gateway</text>'
. '<text x="100" y="7" font-size="5" fill="#4CAF50" font-family="Inter,sans-serif" text-anchor="end">OK</text>'
. '</g>'
. '<g transform="translate(10,38)">'
. '<circle cx="5" cy="4" r="3" fill="#4CAF50"/>'
. '<text x="12" y="7" font-size="5.5" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">Database</text>'
. '<text x="100" y="7" font-size="5" fill="#4CAF50" font-family="Inter,sans-serif" text-anchor="end">OK</text>'
. '</g>'
. '<g transform="translate(10,52)">'
. '<circle cx="5" cy="4" r="3" fill="#f59e0b"/>'
. '<text x="12" y="7" font-size="5.5" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">Cache Layer</text>'
. '<text x="100" y="7" font-size="5" fill="#f59e0b" font-family="Inter,sans-serif" text-anchor="end">WARN</text>'
. '</g>'
. '<g transform="translate(10,66)">'
. '<circle cx="5" cy="4" r="3" fill="#4CAF50"/>'
. '<text x="12" y="7" font-size="5.5" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">CDN</text>'
. '<text x="100" y="7" font-size="5" fill="#4CAF50" font-family="Inter,sans-serif" text-anchor="end">OK</text>'
. '</g>'
. '<g transform="translate(10,80)">'
. '<circle cx="5" cy="4" r="3" fill="#4CAF50"/>'
. '<text x="12" y="7" font-size="5.5" fill="rgba(255,255,255,.6)" font-family="Inter,sans-serif">Workers</text>'
. '<text x="100" y="7" font-size="5" fill="#4CAF50" font-family="Inter,sans-serif" text-anchor="end">OK</text>'
. '</g>'
/* Uptime badge */
. '<rect x="10" y="95" width="97" height="18" rx="3" fill="#252540" />'
. '<text x="58" y="107" font-size="7" fill="#4CAF50" font-family="Inter,sans-serif" text-anchor="middle" font-weight="700">99.98% Uptime</text>'
/* Monitor stand */
. '<rect x="48" y="130" width="29" height="10" fill="#222" />'
. '<rect x="33" y="138" width="59" height="3" rx="1" fill="#333" />'
. '</g>'
/* ── Desk surface ── */
. '<rect x="0" y="160" width="400" height="4" fill="#333" />'
/* Labels */
. '<text x="72" y="180" font-size="7" fill="rgba(255,255,255,.25)" font-family="Inter,sans-serif" text-anchor="middle" font-weight="600" letter-spacing=".1em">THROUGHPUT</text>'
. '<text x="200" y="180" font-size="7" fill="rgba(255,255,255,.25)" font-family="Inter,sans-serif" text-anchor="middle" font-weight="600" letter-spacing=".1em">DISTRIBUTION</text>'
. '<text x="327" y="180" font-size="7" fill="rgba(255,255,255,.25)" font-family="Inter,sans-serif" text-anchor="middle" font-weight="600" letter-spacing=".1em">SYSTEM STATUS</text>'
. '</svg></div>';
$visual_cls = 'platform-visual has-industry';
} 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>
<?php endif; ?>
</div>
<div class="<?php echo esc_attr( $visual_cls ); ?>"><?php echo $visual_html; ?></div>
</div>
<?php return ob_get_clean();
}
/* ── Trust Section (parent - wraps child trust-item blocks) ────────────────── */
function oribi_render_trust_section( $a, $content ) {
ob_start(); ?>
<section class="section">
<div class="container">
<div class="section-header">
<?php if ( $a['label'] ) : ?><span class="section-label"><?php echo esc_html( $a['label'] ); ?></span><?php endif; ?>
<h2><?php echo wp_kses_post( $a['heading'] ); ?></h2>
<?php if ( $a['lead'] ) : ?><p class="lead"><?php echo wp_kses_post( $a['lead'] ); ?></p><?php endif; ?>
</div>
<div class="grid-2" style="align-items:center;">
<div style="display:flex;flex-direction:column;gap:1.5rem;">
<?php echo $content; ?>
</div>
<div style="text-align:center;">
<a href="<?php echo esc_url( $a['btnUrl'] ); ?>" target="_blank" rel="noopener" class="btn btn-primary btn-lg"><?php echo esc_html( $a['btnText'] ); ?></a>
<?php if ( $a['btnSub'] ) : ?><p class="lead mt-2" style="font-size:.9rem;"><?php echo esc_html( $a['btnSub'] ); ?></p><?php endif; ?>
</div>
</div>
</div>
</section>
<?php return ob_get_clean();
}
/* ── Trust Item (child - renders one heading + description pair) ────────────── */
function oribi_render_trust_item( $a ) {
ob_start(); ?>
<div class="trust-item">
<h3 style="margin-bottom:1rem;"><?php echo wp_kses_post( $a['heading'] ); ?></h3>
<p><?php echo wp_kses_post( $a['description'] ); ?></p>
</div>
<?php return ob_get_clean();
}
/* ── FAQ Section (parent - wraps child faq-item blocks) ────────────────────── */
function oribi_render_faq_section( $a, $content ) {
$cls = $a['variant'] === 'alt' ? 'section section-alt' : 'section';
ob_start(); ?>
<section class="<?php echo esc_attr( $cls ); ?>">
<div class="container">
<div class="section-header">
<?php if ( $a['label'] ) : ?><span class="section-label"><?php echo esc_html( $a['label'] ); ?></span><?php endif; ?>
<h2><?php echo wp_kses_post( $a['heading'] ); ?></h2>
<?php if ( $a['lead'] ) : ?><p class="lead"><?php echo wp_kses_post( $a['lead'] ); ?></p><?php endif; ?>
</div>
<div class="faq-list">
<?php echo $content; ?>
</div>
</div>
</section>
<?php return ob_get_clean();
}
/* ── FAQ Item (child - renders one accordion item) ─────────────────────────── */
function oribi_render_faq_item( $a ) {
ob_start(); ?>
<details class="faq-item">
<summary class="faq-question"><?php echo wp_kses_post( $a['question'] ); ?><span class="faq-toggle" aria-hidden="true"></span></summary>
<div class="faq-answer"><p><?php echo wp_kses_post( $a['answer'] ); ?></p></div>
</details>
<?php return ob_get_clean();
}
/* ── Comparison Table (standalone - renders a feature matrix) ──────────────── */
function oribi_render_comparison_table( $a ) {
$cls = $a['variant'] === 'alt' ? 'section section-alt' : 'section';
$cols = $a['columns'] ?? [];
$rows = $a['rows'] ?? [];
ob_start(); ?>
<section class="<?php echo esc_attr( $cls ); ?>">
<div class="container">
<div class="section-header">
<?php if ( $a['label'] ) : ?><span class="section-label"><?php echo esc_html( $a['label'] ); ?></span><?php endif; ?>
<h2><?php echo wp_kses_post( $a['heading'] ); ?></h2>
<?php if ( $a['lead'] ) : ?><p class="lead"><?php echo wp_kses_post( $a['lead'] ); ?></p><?php endif; ?>
</div>
<div class="comparison-table-wrap">
<table class="comparison-table">
<thead>
<tr>
<th class="comparison-feature-col">Feature</th>
<?php foreach ( $cols as $col ) : ?>
<th><?php echo esc_html( $col ); ?></th>
<?php endforeach; ?>
</tr>
</thead>
<tbody>
<?php foreach ( $rows as $row ) :
$is_group = ! empty( $row['group'] );
?>
<?php if ( $is_group ) : ?>
<tr class="comparison-group-row">
<td colspan="<?php echo count( $cols ) + 1; ?>"><?php echo esc_html( $row['group'] ); ?></td>
</tr>
<?php else : ?>
<tr>
<td class="comparison-feature-name"><?php echo wp_kses_post( $row['feature'] ?? '' ); ?></td>
<?php foreach ( ( $row['values'] ?? [] ) as $val ) : ?>
<td class="comparison-cell"><?php
if ( $val === true ) echo '<span class="comparison-yes">&#10003;</span>';
elseif ( $val === false ) echo '<span class="comparison-no">&#10007;</span>';
else echo wp_kses_post( $val );
?></td>
<?php endforeach; ?>
</tr>
<?php endif; ?>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</section>
<?php return ob_get_clean();
}
/* ══════════════════════════════════════════════════════════════════════════════
ANIMATED HERO BLOCKS (OTS Signs)
══════════════════════════════════════════════════════════════════════════════ */
/**
* Generate particle divs for the animated hero background.
* Each particle gets a unique modifier class for staggered animation.
*
* @param int $count Number of particles (default 12).
* @return string Rendered HTML.
*/
function oribi_render_particles( $count = 12 ) {
$html = '<div class="hero-particles" aria-hidden="true">';
for ( $i = 1; $i <= $count; $i++ ) {
$html .= '<div class="hero-particle hero-particle--' . $i . '"></div>';
}
$html .= '</div>';
return $html;
}
/* ── Animated Hero (homepage) ──────────────────────────────────────────────── */
function oribi_render_hero_animated( $a ) {
ob_start();
?>
<section class="hero hero-animated">
<?php echo oribi_render_particles( 12 ); ?>
<div class="hero-animated__glow"></div>
<div class="container hero-animated__inner">
<div class="hero-animated__content">
<?php if ( $a['label'] ) : ?>
<span class="hero-label"><?php echo wp_kses_post( $a['label'] ); ?></span>
<?php endif; ?>
<h1 class="hero-title"><?php echo oribi_highlight( $a['title'], $a['highlightWord'] ); ?></h1>
<p class="hero-description"><?php echo wp_kses_post( $a['description'] ); ?></p>
<div class="btn-group">
<?php if ( $a['primaryBtnText'] ) : ?>
<a href="<?php echo esc_url( $a['primaryBtnUrl'] ); ?>" class="btn btn-primary btn-lg"><?php echo esc_html( $a['primaryBtnText'] ); ?></a>
<?php endif; ?>
<?php if ( $a['secondaryBtnText'] ) : ?>
<a href="<?php echo esc_url( $a['secondaryBtnUrl'] ); ?>" class="btn btn-ghost btn-lg"><?php echo esc_html( $a['secondaryBtnText'] ); ?> &rarr;</a>
<?php endif; ?>
</div>
<?php if ( $a['stat1Value'] || $a['stat2Value'] || $a['stat3Value'] ) : ?>
<div class="hero-stats hero-stats--three">
<?php if ( $a['stat1Value'] ) : ?>
<div><div class="hero-stat-value"><?php echo esc_html( $a['stat1Value'] ); ?></div><div class="hero-stat-label"><?php echo esc_html( $a['stat1Label'] ); ?></div></div>
<?php endif; ?>
<?php if ( $a['stat2Value'] ) : ?>
<div><div class="hero-stat-value"><?php echo esc_html( $a['stat2Value'] ); ?></div><div class="hero-stat-label"><?php echo esc_html( $a['stat2Label'] ); ?></div></div>
<?php endif; ?>
<?php if ( $a['stat3Value'] ) : ?>
<div><div class="hero-stat-value"><?php echo esc_html( $a['stat3Value'] ); ?></div><div class="hero-stat-label"><?php echo esc_html( $a['stat3Label'] ); ?></div></div>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
</div>
</section>
<?php return ob_get_clean();
}
/* ── Animated Page Hero (inner pages) ──────────────────────────────────────── */
function oribi_render_page_hero_animated( $a ) {
ob_start(); ?>
<section class="page-hero page-hero-animated">
<?php echo oribi_render_particles( 8 ); ?>
<div class="hero-animated__glow"></div>
<div class="hero-overlay"></div>
<div class="container">
<?php if ( $a['label'] ) : ?><span class="hero-label"><?php echo wp_kses_post( $a['label'] ); ?></span><?php endif; ?>
<h1><?php echo wp_kses_post( $a['title'] ); ?></h1>
<p class="lead"><?php echo wp_kses_post( $a['description'] ); ?></p>
</div>
</section>
<?php return ob_get_clean();
}
/* ── Use Cases Showcase ────────────────────────────────────────────────── */
function oribi_uc_anim_inner( $mod ) {
switch ( $mod ) {
case 'menu':
return '
<div class="uc-inner uc-inner--menu" aria-hidden="true">
<div class="uc-menu-header"></div>
<div class="uc-menu-row"><span class="uc-menu-name"></span><span class="uc-menu-price"></span></div>
<div class="uc-menu-row uc-menu-row--highlight"><span class="uc-menu-name"></span><span class="uc-menu-price"></span></div>
<div class="uc-menu-row"><span class="uc-menu-name uc-menu-name--sm"></span><span class="uc-menu-price"></span></div>
<div class="uc-menu-row"><span class="uc-menu-name"></span><span class="uc-menu-price"></span></div>
<div class="uc-menu-divider"></div>
<div class="uc-menu-row"><span class="uc-menu-name uc-menu-name--sm"></span><span class="uc-menu-price"></span></div>
</div>';
case 'event':
return '
<div class="uc-inner uc-inner--event" aria-hidden="true">
<div class="uc-event-cursor"></div>
<div class="uc-event-header">Schedule</div>
<div class="uc-event-row">
<div class="uc-event-time">9:00</div>
<div class="uc-event-title uc-event-title--active">Keynote Speech</div>
</div>
<div class="uc-event-row">
<div class="uc-event-time">10:30</div>
<div class="uc-event-title">Panel Discussion</div>
</div>
<div class="uc-event-row">
<div class="uc-event-time">11:45</div>
<div class="uc-event-title uc-event-title--accent">Coffee Break</div>
</div>
<div class="uc-event-row">
<div class="uc-event-time">12:30</div>
<div class="uc-event-title">Networking Lunch</div>
</div>
</div>';
case 'dashboard':
return '
<div class="uc-inner uc-inner--dashboard" aria-hidden="true">
<div class="uc-db-bars">
<div class="uc-db-bar uc-db-bar--1"></div>
<div class="uc-db-bar uc-db-bar--2"></div>
<div class="uc-db-bar uc-db-bar--3"></div>
<div class="uc-db-bar uc-db-bar--4"></div>
</div>
<div class="uc-db-baseline"></div>
<div class="uc-db-labels">
<div class="uc-db-lbl"></div>
<div class="uc-db-lbl"></div>
<div class="uc-db-lbl"></div>
<div class="uc-db-lbl"></div>
</div>
</div>';
case 'wayfinding':
return '
<div class="uc-inner uc-inner--wayfinding" aria-hidden="true">
<div class="uc-wf-corridor uc-wf-corridor--h"></div>
<div class="uc-wf-corridor uc-wf-corridor--v"></div>
<div class="uc-wf-room uc-wf-room--1"></div>
<div class="uc-wf-room uc-wf-room--2"></div>
<div class="uc-wf-room uc-wf-room--3"></div>
<div class="uc-wf-dot"></div>
<div class="uc-wf-arrow"></div>
</div>';
default:
return '';
}
}
function oribi_render_use_cases( $a ) {
$cases = [
[ 'title' => $a['case1Title'], 'desc' => $a['case1Desc'], 'mod' => 'menu' ],
[ 'title' => $a['case2Title'], 'desc' => $a['case2Desc'], 'mod' => 'event' ],
[ 'title' => $a['case3Title'], 'desc' => $a['case3Desc'], 'mod' => 'dashboard' ],
[ 'title' => $a['case4Title'], 'desc' => $a['case4Desc'], 'mod' => 'wayfinding' ],
];
ob_start(); ?>
<section class="section use-cases-section">
<div class="container">
<?php if ( $a['label'] || $a['heading'] || $a['lead'] ) : ?>
<div class="section-header">
<?php if ( $a['label'] ) : ?><span class="section-label"><?php echo esc_html( $a['label'] ); ?></span><?php endif; ?>
<?php if ( $a['heading'] ) : ?><h2><?php echo wp_kses_post( $a['heading'] ); ?></h2><?php endif; ?>
<?php if ( $a['lead'] ) : ?><p class="lead"><?php echo wp_kses_post( $a['lead'] ); ?></p><?php endif; ?>
</div>
<?php endif; ?>
<div class="uc-track">
<?php foreach ( $cases as $c ) : ?>
<div class="uc-item">
<div class="uc-circle uc-anim--<?php echo esc_attr( $c['mod'] ); ?>">
<?php echo oribi_uc_anim_inner( $c['mod'] ); ?>
</div>
<div class="uc-item-body">
<div class="uc-item-title"><?php echo esc_html( $c['title'] ); ?></div>
<?php if ( $c['desc'] ) : ?><p class="uc-item-desc"><?php echo esc_html( $c['desc'] ); ?></p><?php endif; ?>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
</section>
<?php return ob_get_clean();
}