Files
OTSSigns-Website/theme/blocks/index.php

1765 lines
90 KiB
PHP
Raw Normal View History

2026-02-20 21:28:00 -05:00
<?php
/**
* OTS Theme - Custom Gutenberg Blocks (InnerBlocks Architecture)
2026-02-20 21:28:00 -05:00
*
* 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)
2026-02-20 21:28:00 -05:00
*
* 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
2026-02-20 21:28:00 -05:00
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' => '' ],
2026-02-20 21:28:00 -05:00
'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 ],
2026-02-20 21:28:00 -05:00
],
'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 */
2026-02-20 21:28:00 -05:00
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 */
2026-02-20 21:28:00 -05:00
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',
] );
} );
/* ══════════════════════════════════════════════════════════════════════════════
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>';
2026-02-20 21:28:00 -05:00
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>
2026-02-20 21:28:00 -05:00
</div>
<div class="footer-links">
<div class="footer-col">
<h4>Platform</h4>
2026-02-20 21:28:00 -05:00
<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>
2026-02-20 21:28:00 -05:00
</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>
2026-02-20 21:28:00 -05:00
<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>
2026-02-20 21:28:00 -05:00
</ul>
</div>
</div>
</div>
<div class="footer-bottom">
<p>&copy; <?php echo esc_html( $year ); ?> Oribi Technology Services. All rights reserved.</p>
2026-02-20 21:28:00 -05:00
</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>
2026-02-20 21:28:00 -05:00
</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>
2026-02-20 21:28:00 -05:00
<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) ────────────── */
2026-02-20 21:28:00 -05:00
function oribi_render_feature_section( $a, $content ) {
return oribi_render_card_section( $a, $content, 'grid', 3 );
}
/* ── Feature Card (child - renders one card) ───────────────────────────────── */
2026-02-20 21:28:00 -05:00
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) ────────────── */
2026-02-20 21:28:00 -05:00
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) ───────────────────────── */
2026-02-20 21:28:00 -05:00
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; ?>
2026-02-20 21:28:00 -05:00
<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) ───────────── */
2026-02-20 21:28:00 -05:00
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) ────────────────────────── */
2026-02-20 21:28:00 -05:00
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="#004225" 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="#004225" 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="#004225"/>
<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 ) {
2026-02-20 21:28:00 -05:00
$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"></div>';
$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['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';
2026-02-20 21:28:00 -05:00
} 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) ────────────────── */
2026-02-20 21:28:00 -05:00
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) ────────────── */
2026-02-20 21:28:00 -05:00
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) ────────────────────── */
2026-02-20 21:28:00 -05:00
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) ─────────────────────────── */
2026-02-20 21:28:00 -05:00
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) ──────────────── */
2026-02-20 21:28:00 -05:00
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>';
2026-02-20 21:28:00 -05:00
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();
}