Skip to main content

Theme Customizations

Blocker Outdoors uses Suma Elementor v3.0.0, a custom WordPress theme built on Elementor Pro with 60+ specialized widgets for e-commerce and content presentation.

Theme Architecture

Technology Stack

TechnologyVersionPurpose
Base ThemeSuma Elementor v3.0.0Custom parent theme
Elementor Prov3.28.3Page builder framework
Laravel Mixv6.0.49Asset compilation and bundling
Preactv10.11.0Lightweight React alternative for widgets
Tailwind CSSv3.4Utility-first CSS framework
PostCSSv8.4CSS processing and optimization
Sassv1.77SCSS preprocessing

Directory Structure

wp-content/themes/suma-elementor/
├── assets/
│ ├── src/
│ │ ├── js/
│ │ │ ├── components/ # Preact components
│ │ │ │ ├── cart-widget.jsx
│ │ │ │ ├── product-grid.jsx
│ │ │ │ ├── variant-selector.jsx
│ │ │ │ └── search-autocomplete.jsx
│ │ │ ├── modules/
│ │ │ │ ├── analytics.js
│ │ │ │ ├── cart.js
│ │ │ │ └── product.js
│ │ │ └── app.js # Main entry point
│ │ ├── scss/
│ │ │ ├── base/
│ │ │ ├── components/
│ │ │ ├── layouts/
│ │ │ ├── utilities/
│ │ │ └── app.scss # Main entry point
│ │ └── images/
│ ├── dist/ # Compiled assets (gitignored)
│ │ ├── css/
│ │ │ └── app.css
│ │ └── js/
│ │ ├── app.js
│ │ └── app.js.map
├── elementor/
│ ├── widgets/ # Custom Elementor widgets
│ │ ├── product-grid.php
│ │ ├── product-carousel.php
│ │ ├── product-comparison.php
│ │ ├── dealer-locator.php
│ │ ├── hero-slider.php
│ │ ├── testimonial-grid.php
│ │ └── ... (60+ widgets)
│ ├── controls/ # Custom Elementor controls
│ └── dynamic-tags/ # Dynamic content tags
├── inc/
│ ├── theme-setup.php
│ ├── custom-post-types.php
│ ├── template-functions.php
│ └── bigcommerce-overrides.php # BC template customizations
├── template-parts/
│ ├── header/
│ ├── footer/
│ └── content/
├── woocommerce/ # Not used (BigCommerce only)
├── bigcommerce/ # BigCommerce template overrides
│ ├── single-product.php
│ ├── product-card.php
│ ├── cart.php
│ └── components/
│ ├── product-gallery.php
│ ├── product-meta.php
│ └── variant-options.php
├── functions.php
├── style.css
├── package.json
├── webpack.mix.js
└── tailwind.config.js

Build System (Laravel Mix)

Configuration

webpack.mix.js:

const mix = require('laravel-mix');
const path = require('path');

/*
|--------------------------------------------------------------------------
| Mix Asset Management
|--------------------------------------------------------------------------
*/

mix
// JavaScript compilation
.js('assets/src/js/app.js', 'assets/dist/js')
.preact() // Enable Preact compilation
.extract() // Vendor code splitting

// SCSS compilation
.sass('assets/src/scss/app.scss', 'assets/dist/css')
.options({
postCss: [
require('tailwindcss'),
require('autoprefixer'),
],
processCssUrls: false,
})

// Copy static assets
.copyDirectory('assets/src/images', 'assets/dist/images')

// Versioning for cache busting (production only)
.version()

// Source maps (development only)
.sourceMaps(!mix.inProduction())

// Browser sync for live reload (development)
.browserSync({
proxy: 'blockeroutdoors.local',
files: [
'assets/dist/**/*',
'**/*.php',
],
})

// Webpack configuration
.webpackConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, 'assets/src/js'),
'@components': path.resolve(__dirname, 'assets/src/js/components'),
'@modules': path.resolve(__dirname, 'assets/src/js/modules'),
},
},
externals: {
'jquery': 'jQuery',
},
});

// Production optimizations
if (mix.inProduction()) {
mix
.minify('assets/dist/css/app.css')
.minify('assets/dist/js/app.js')
.options({
terser: {
extractComments: false,
},
});
}

tailwind.config.js:

const colors = require('tailwindcss/colors');

module.exports = {
content: [
'./**/*.php',
'./assets/src/js/**/*.{js,jsx}',
'./elementor/widgets/**/*.php',
],
theme: {
extend: {
colors: {
// Blocker Outdoors brand colors
primary: {
50: '#f0fdf4',
100: '#dcfce7',
200: '#bbf7d0',
300: '#86efac',
400: '#4ade80',
500: '#2c5f2d', // Primary brand green
600: '#16a34a',
700: '#15803d',
800: '#166534',
900: '#14532d',
},
accent: {
DEFAULT: '#97c93d', // Lime green accent
light: '#b4dc58',
dark: '#7aa82f',
},
camo: {
DEFAULT: '#4a5134',
light: '#6b7352',
dark: '#2d3120',
},
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
heading: ['Oswald', 'system-ui', 'sans-serif'],
display: ['Bebas Neue', 'system-ui', 'sans-serif'],
},
fontSize: {
'display': ['4.5rem', { lineHeight: '1', letterSpacing: '-0.02em' }],
},
spacing: {
'18': '4.5rem',
'88': '22rem',
'128': '32rem',
},
maxWidth: {
'8xl': '88rem',
'9xl': '96rem',
},
zIndex: {
'60': '60',
'70': '70',
'80': '80',
'90': '90',
'100': '100',
},
},
},
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/typography'),
require('@tailwindcss/aspect-ratio'),
require('@tailwindcss/container-queries'),
],
};

Build Commands

# Development build (watch mode)
npm run dev

# Production build (minified)
npm run production

# Watch for changes and rebuild
npm run watch

# Build with hot module replacement
npm run hot

Asset Enqueueing

functions.php:

<?php
/**
* Enqueue scripts and styles
*/
function suma_enqueue_assets() {
$manifest = include get_template_directory() . '/assets/dist/mix-manifest.php';

// Main stylesheet
wp_enqueue_style(
'suma-app',
get_template_directory_uri() . '/assets/dist/css/app.css',
[],
$manifest['/css/app.css'] ?? '1.0.0'
);

// Vendor JavaScript (extracted dependencies)
wp_enqueue_script(
'suma-vendor',
get_template_directory_uri() . '/assets/dist/js/vendor.js',
[],
$manifest['/js/vendor.js'] ?? '1.0.0',
true
);

// Main JavaScript bundle
wp_enqueue_script(
'suma-app',
get_template_directory_uri() . '/assets/dist/js/app.js',
[ 'suma-vendor', 'jquery' ],
$manifest['/js/app.js'] ?? '1.0.0',
true
);

// Localize script with PHP data
wp_localize_script( 'suma-app', 'sumaConfig', [
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'restUrl' => rest_url(),
'nonce' => wp_create_nonce( 'wp_rest' ),
'bigcommerce' => [
'storeUrl' => defined( 'BIGCOMMERCE_STORE_URL' ) ? BIGCOMMERCE_STORE_URL : '',
'channelId' => defined( 'BIGCOMMERCE_CHANNEL_ID' ) ? BIGCOMMERCE_CHANNEL_ID : '',
],
'klaviyo' => [
'publicKey' => get_option( 'klaviyo_public_key' ),
],
'algolia' => [
'appId' => defined( 'ALGOLIA_APP_ID' ) ? ALGOLIA_APP_ID : '',
'searchKey' => defined( 'ALGOLIA_SEARCH_KEY' ) ? ALGOLIA_SEARCH_KEY : '',
'indexName' => 'blocker_products',
],
] );
}
add_action( 'wp_enqueue_scripts', 'suma_enqueue_assets' );

Custom Elementor Widgets

Suma Elementor v3.0.0 includes 60+ custom widgets. Here are the key e-commerce widgets:

Product Grid Widget

elementor/widgets/product-grid.php:

<?php
namespace Suma_Elementor\Widgets;

use Elementor\Widget_Base;
use Elementor\Controls_Manager;

class Product_Grid extends Widget_Base {

public function get_name() {
return 'suma_product_grid';
}

public function get_title() {
return __( 'Product Grid', 'suma-elementor' );
}

public function get_icon() {
return 'eicon-products';
}

public function get_categories() {
return [ 'suma-ecommerce' ];
}

protected function register_controls() {

// Content Section
$this->start_controls_section(
'content_section',
[
'label' => __( 'Content', 'suma-elementor' ),
'tab' => Controls_Manager::TAB_CONTENT,
]
);

$this->add_control(
'product_source',
[
'label' => __( 'Product Source', 'suma-elementor' ),
'type' => Controls_Manager::SELECT,
'default' => 'category',
'options' => [
'category' => __( 'Category', 'suma-elementor' ),
'featured' => __( 'Featured Products', 'suma-elementor' ),
'sale' => __( 'On Sale', 'suma-elementor' ),
'new' => __( 'New Arrivals', 'suma-elementor' ),
'manual' => __( 'Manual Selection', 'suma-elementor' ),
],
]
);

$this->add_control(
'category_ids',
[
'label' => __( 'Categories', 'suma-elementor' ),
'type' => Controls_Manager::SELECT2,
'multiple' => true,
'options' => $this->get_product_categories(),
'condition' => [
'product_source' => 'category',
],
]
);

$this->add_control(
'products_per_page',
[
'label' => __( 'Products Per Page', 'suma-elementor' ),
'type' => Controls_Manager::NUMBER,
'default' => 12,
'min' => 1,
'max' => 100,
]
);

$this->add_control(
'columns',
[
'label' => __( 'Columns', 'suma-elementor' ),
'type' => Controls_Manager::SELECT,
'default' => '4',
'options' => [
'2' => '2',
'3' => '3',
'4' => '4',
'5' => '5',
'6' => '6',
],
]
);

$this->add_control(
'show_filters',
[
'label' => __( 'Show Filters', 'suma-elementor' ),
'type' => Controls_Manager::SWITCHER,
'default' => 'yes',
]
);

$this->add_control(
'show_sorting',
[
'label' => __( 'Show Sorting', 'suma-elementor' ),
'type' => Controls_Manager::SWITCHER,
'default' => 'yes',
]
);

$this->end_controls_section();

// Style Section
$this->start_controls_section(
'style_section',
[
'label' => __( 'Style', 'suma-elementor' ),
'tab' => Controls_Manager::TAB_STYLE,
]
);

$this->add_control(
'card_style',
[
'label' => __( 'Card Style', 'suma-elementor' ),
'type' => Controls_Manager::SELECT,
'default' => 'standard',
'options' => [
'standard' => __( 'Standard', 'suma-elementor' ),
'minimal' => __( 'Minimal', 'suma-elementor' ),
'overlay' => __( 'Overlay', 'suma-elementor' ),
],
]
);

$this->end_controls_section();
}

protected function render() {
$settings = $this->get_settings_for_display();

$query_args = [
'post_type' => 'bigcommerce_product',
'posts_per_page' => $settings['products_per_page'],
'post_status' => 'publish',
];

// Build query based on source
if ( $settings['product_source'] === 'category' && ! empty( $settings['category_ids'] ) ) {
$query_args['tax_query'] = [
[
'taxonomy' => 'bigcommerce_category',
'field' => 'term_id',
'terms' => $settings['category_ids'],
],
];
} elseif ( $settings['product_source'] === 'featured' ) {
$query_args['meta_query'] = [
[
'key' => 'bigcommerce_featured',
'value' => '1',
],
];
} elseif ( $settings['product_source'] === 'sale' ) {
$query_args['meta_query'] = [
[
'key' => 'bigcommerce_sale_price',
'value' => 0,
'compare' => '>',
'type' => 'NUMERIC',
],
];
}

$products = new \WP_Query( $query_args );

if ( $products->have_posts() ) :
?>
<div class="suma-product-grid" data-columns="<?php echo esc_attr( $settings['columns'] ); ?>">

<?php if ( $settings['show_filters'] === 'yes' ) : ?>
<div class="product-filters">
<?php echo do_shortcode( '[facetwp template="filters"]' ); ?>
</div>
<?php endif; ?>

<?php if ( $settings['show_sorting'] === 'yes' ) : ?>
<div class="product-sorting">
<?php echo do_shortcode( '[facetwp sort="true"]' ); ?>
</div>
<?php endif; ?>

<div class="product-grid grid grid-cols-<?php echo esc_attr( $settings['columns'] ); ?> gap-6">
<?php
while ( $products->have_posts() ) :
$products->the_post();
$this->render_product_card( $settings['card_style'] );
endwhile;
wp_reset_postdata();
?>
</div>

<?php if ( $products->max_num_pages > 1 ) : ?>
<div class="product-pagination">
<?php echo do_shortcode( '[facetwp pager="true"]' ); ?>
</div>
<?php endif; ?>

</div>
<?php
endif;
}

private function render_product_card( $style = 'standard' ) {
$product_id = get_the_ID();
$bc_id = get_post_meta( $product_id, 'bigcommerce_id', true );
$price = get_post_meta( $product_id, 'bigcommerce_price', true );
$sale_price = get_post_meta( $product_id, 'bigcommerce_sale_price', true );
$availability = get_post_meta( $product_id, 'bigcommerce_availability', true );

?>
<article class="product-card product-card--<?php echo esc_attr( $style ); ?>" data-product-id="<?php echo esc_attr( $bc_id ); ?>">
<div class="product-card__image">
<a href="<?php the_permalink(); ?>">
<?php the_post_thumbnail( 'medium', [ 'class' => 'w-full h-auto' ] ); ?>
</a>

<?php if ( $availability !== 'available' ) : ?>
<span class="product-badge product-badge--out-of-stock">
<?php _e( 'Out of Stock', 'suma-elementor' ); ?>
</span>
<?php elseif ( $sale_price > 0 && $sale_price < $price ) : ?>
<span class="product-badge product-badge--sale">
<?php _e( 'Sale', 'suma-elementor' ); ?>
</span>
<?php endif; ?>

<div class="product-card__quick-actions">
<button class="btn-quick-view" data-product-id="<?php echo esc_attr( $bc_id ); ?>">
<?php _e( 'Quick View', 'suma-elementor' ); ?>
</button>
</div>
</div>

<div class="product-card__content">
<h3 class="product-card__title">
<a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
</h3>

<div class="product-card__price">
<?php if ( $sale_price > 0 && $sale_price < $price ) : ?>
<span class="price price--regular"><?php echo esc_html( '$' . number_format( $price, 2 ) ); ?></span>
<span class="price price--sale"><?php echo esc_html( '$' . number_format( $sale_price, 2 ) ); ?></span>
<?php else : ?>
<span class="price"><?php echo esc_html( '$' . number_format( $price, 2 ) ); ?></span>
<?php endif; ?>
</div>

<div class="product-card__actions">
<button class="btn btn-primary btn-add-to-cart" data-product-id="<?php echo esc_attr( $bc_id ); ?>">
<?php _e( 'Add to Cart', 'suma-elementor' ); ?>
</button>
</div>
</div>
</article>
<?php
}

private function get_product_categories() {
$categories = get_terms( [
'taxonomy' => 'bigcommerce_category',
'hide_empty' => false,
] );

$options = [];
foreach ( $categories as $category ) {
$options[ $category->term_id ] = $category->name;
}

return $options;
}
}

Advanced carousel with Swiper.js integration for featured products, related items, and promotional displays.

Variant Selector Widget (Preact)

assets/src/js/components/variant-selector.jsx:

import { h, Component } from 'preact';
import { useState, useEffect } from 'preact/hooks';

export default function VariantSelector({ productId, variants, onVariantChange }) {
const [selectedOptions, setSelectedOptions] = useState({});
const [selectedVariant, setSelectedVariant] = useState(null);
const [price, setPrice] = useState(null);
const [availability, setAvailability] = useState('available');

// Get unique option types
const optionTypes = [...new Set(variants.flatMap(v =>
v.option_values.map(ov => ov.option_display_name)
))];

// Get option values for each option type
const getOptionValues = (optionType) => {
const values = new Set();
variants.forEach(variant => {
const option = variant.option_values.find(ov =>
ov.option_display_name === optionType
);
if (option) {
values.add(option.label);
}
});
return Array.from(values);
};

// Handle option selection
const handleOptionChange = (optionType, value) => {
const newOptions = {
...selectedOptions,
[optionType]: value
};
setSelectedOptions(newOptions);

// Find matching variant
const variant = variants.find(v => {
return Object.keys(newOptions).every(optType => {
const option = v.option_values.find(ov =>
ov.option_display_name === optType
);
return option && option.label === newOptions[optType];
});
});

if (variant) {
setSelectedVariant(variant);
setPrice(variant.sale_price || variant.price);
setAvailability(variant.inventory_level > 0 ? 'available' : 'out_of_stock');

if (onVariantChange) {
onVariantChange(variant);
}
}
};

return (
<div className="variant-selector">
{optionTypes.map(optionType => (
<div key={optionType} className="variant-selector__option">
<label className="variant-selector__label">
{optionType}:
{selectedOptions[optionType] && (
<span className="variant-selector__selected">
{' '}{selectedOptions[optionType]}
</span>
)}
</label>

<div className="variant-selector__values">
{getOptionValues(optionType).map(value => (
<button
key={value}
type="button"
className={`variant-selector__button ${
selectedOptions[optionType] === value ? 'active' : ''
}`}
onClick={() => handleOptionChange(optionType, value)}
>
{value}
</button>
))}
</div>
</div>
))}

{price && (
<div className="variant-selector__price">
<span className="price">${price.toFixed(2)}</span>
</div>
)}

{availability === 'out_of_stock' && (
<div className="variant-selector__availability">
<span className="out-of-stock">Out of Stock</span>
</div>
)}
</div>
);
}

BigCommerce Template Overrides

Custom templates in bigcommerce/ directory override plugin defaults:

Single Product Template

bigcommerce/single-product.php:

<?php
/**
* Single Product Template Override
* Customized for Blocker Outdoors
*/

get_header();

while ( have_posts() ) :
the_post();

$product_id = get_the_ID();
$bc_id = get_post_meta( $product_id, 'bigcommerce_id', true );
$price = get_post_meta( $product_id, 'bigcommerce_price', true );
$sale_price = get_post_meta( $product_id, 'bigcommerce_sale_price', true );
$sku = get_post_meta( $product_id, 'bigcommerce_sku', true );
$brand = get_post_meta( $product_id, 'bigcommerce_brand', true );
?>

<article id="product-<?php echo esc_attr( $bc_id ); ?>" <?php post_class( 'single-product' ); ?>>

<div class="product-layout container mx-auto px-4 py-8">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">

<!-- Product Images -->
<div class="product-images">
<?php get_template_part( 'bigcommerce/components/product-gallery' ); ?>
</div>

<!-- Product Info -->
<div class="product-info">

<!-- Breadcrumbs -->
<div class="product-breadcrumbs mb-4">
<?php echo do_shortcode( '[rank_math_breadcrumb]' ); ?>
</div>

<!-- Brand -->
<?php if ( $brand ) : ?>
<div class="product-brand mb-2">
<span class="text-sm text-gray-600"><?php echo esc_html( $brand ); ?></span>
</div>
<?php endif; ?>

<!-- Title -->
<h1 class="product-title text-3xl font-heading font-bold mb-4">
<?php the_title(); ?>
</h1>

<!-- Price -->
<div class="product-price mb-6">
<?php if ( $sale_price > 0 && $sale_price < $price ) : ?>
<span class="price price--regular line-through text-gray-500 mr-2">
$<?php echo number_format( $price, 2 ); ?>
</span>
<span class="price price--sale text-3xl font-bold text-accent">
$<?php echo number_format( $sale_price, 2 ); ?>
</span>
<?php else : ?>
<span class="price text-3xl font-bold">
$<?php echo number_format( $price, 2 ); ?>
</span>
<?php endif; ?>
</div>

<!-- Reviews -->
<div class="product-reviews mb-6">
<?php echo do_shortcode( '[bazaarvoice_reviews product_id="' . $bc_id . '"]' ); ?>
</div>

<!-- Description -->
<div class="product-description mb-6">
<?php the_content(); ?>
</div>

<!-- Variant Selector -->
<div class="product-variants mb-6" data-product-id="<?php echo esc_attr( $bc_id ); ?>">
<?php get_template_part( 'bigcommerce/components/variant-options' ); ?>
</div>

<!-- Add to Cart -->
<div class="product-actions mb-6">
<button class="btn btn-primary btn-lg w-full mb-4" id="add-to-cart">
<?php _e( 'Add to Cart', 'suma-elementor' ); ?>
</button>

<!-- Financing Options -->
<div class="product-financing grid grid-cols-2 gap-4">
<div class="klarna-messaging" data-purchase-amount="<?php echo esc_attr( $sale_price ?: $price ); ?>">
<!-- Klarna widget injected via JavaScript -->
</div>
<div class="sezzle-widget" data-amount="<?php echo esc_attr( ( $sale_price ?: $price ) * 100 ); ?>">
<!-- Sezzle widget injected via JavaScript -->
</div>
</div>
</div>

<!-- Product Meta -->
<div class="product-meta text-sm text-gray-600 space-y-2">
<?php if ( $sku ) : ?>
<div>
<strong><?php _e( 'SKU:', 'suma-elementor' ); ?></strong>
<span><?php echo esc_html( $sku ); ?></span>
</div>
<?php endif; ?>

<div>
<strong><?php _e( 'Categories:', 'suma-elementor' ); ?></strong>
<?php echo get_the_term_list( $product_id, 'bigcommerce_category', '', ', ' ); ?>
</div>
</div>

<!-- Back in Stock -->
<div class="product-back-in-stock mt-6">
<?php echo do_shortcode( '[back_in_stock_form product_id="' . $bc_id . '"]' ); ?>
</div>

</div>

</div>

<!-- Product Tabs -->
<div class="product-tabs mt-12">
<?php get_template_part( 'bigcommerce/components/product-tabs' ); ?>
</div>

<!-- Related Products -->
<div class="related-products mt-12">
<h2 class="text-2xl font-heading font-bold mb-6"><?php _e( 'Related Products', 'suma-elementor' ); ?></h2>
<?php echo do_shortcode( '[suma_product_carousel source="related" product_id="' . $bc_id . '"]' ); ?>
</div>

</div>

</article>

<?php
endwhile;

get_footer();

Responsive Design

Mobile-first approach with Tailwind CSS breakpoints:

/* Tailwind breakpoints */
sm: 640px
md: 768px
lg: 1024px
xl: 1280px
2xl: 1536px

/* Custom breakpoints for product grids */
@screen md {
.product-grid[data-columns="4"],
.product-grid[data-columns="5"],
.product-grid[data-columns="6"] {
@apply grid-cols-2;
}
}

@screen lg {
.product-grid[data-columns="4"] {
@apply grid-cols-3;
}
.product-grid[data-columns="5"],
.product-grid[data-columns="6"] {
@apply grid-cols-4;
}
}

@screen xl {
.product-grid[data-columns="4"] {
@apply grid-cols-4;
}
.product-grid[data-columns="5"] {
@apply grid-cols-5;
}
.product-grid[data-columns="6"] {
@apply grid-cols-6;
}
}

Performance Optimizations

Lazy Loading

// Lazy load product images
import 'lazysizes';

document.addEventListener('DOMContentLoaded', () => {
// Add lazyload class to images
document.querySelectorAll('.product-card__image img').forEach(img => {
img.classList.add('lazyload');
img.dataset.src = img.src;
img.src = '/wp-content/themes/suma-elementor/assets/dist/images/placeholder.svg';
});
});

Code Splitting

// Dynamic imports for non-critical components
const loadQuickView = () => import(/* webpackChunkName: "quick-view" */ '@components/quick-view');
const loadProductComparison = () => import(/* webpackChunkName: "comparison" */ '@components/product-comparison');

// Load on demand
document.querySelectorAll('.btn-quick-view').forEach(btn => {
btn.addEventListener('click', async () => {
const { default: QuickView } = await loadQuickView();
const productId = btn.dataset.productId;
QuickView.open(productId);
});
});