Skip to main content

Theme Customizations

Muddy uses Suma Elementor v2.5.0, a custom WordPress theme built by Rhino Group specifically for GSM Outdoors brands. The theme extends Elementor Page Builder with 40+ custom widgets and GSM-specific functionality.

Theme Architecture

Base Theme Structure

themes/suma-elementor/
├── style.css # Theme header and base styles
├── functions.php # Theme initialization
├── header.php # Site header template
├── footer.php # Site footer template
├── single.php # Single post template
├── archive.php # Archive template
├── page.php # Page template
├── 404.php # 404 error template
├── assets/
│ ├── css/
│ │ ├── app.css # Compiled Tailwind CSS
│ │ └── editor.css # Elementor editor styles
│ ├── js/
│ │ ├── app.js # Compiled JavaScript
│ │ └── modules/ # ES6 modules
│ └── images/
├── inc/
│ ├── theme-setup.php # Theme configuration
│ ├── enqueue-scripts.php # Asset loading
│ ├── template-tags.php # Helper functions
│ ├── customizer.php # WordPress Customizer
│ └── widgets/ # Custom widgets (legacy)
├── template-parts/
│ ├── header/ # Header variations
│ ├── footer/ # Footer variations
│ └── content/ # Content templates
├── elementor/
│ ├── widgets/ # Custom Elementor widgets
│ ├── controls/ # Custom Elementor controls
│ └── extensions/ # Elementor extensions
├── woocommerce/ # WooCommerce template overrides
├── bigcommerce/ # BigCommerce template overrides
├── resources/ # Laravel Mix source files
│ ├── css/
│ │ └── app.css # Tailwind CSS entry
│ ├── js/
│ │ └── app.js # JavaScript entry
│ └── views/ # Blade-like templates (optional)
├── webpack.mix.js # Laravel Mix configuration
├── tailwind.config.js # Tailwind CSS configuration
├── package.json # npm dependencies
└── composer.json # PHP dependencies

Custom Elementor Widgets

Suma Elementor includes 40+ custom widgets for GSM Outdoors e-commerce functionality.

Product Widgets

Product Grid Widget

Displays BigCommerce products in a responsive grid with filtering and sorting.

// elementor/widgets/product-grid.php

namespace Suma_Elementor\Widgets;

class Product_Grid extends \Elementor\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() {
// Layout section
$this->start_controls_section(
'layout_section',
[
'label' => __( 'Layout', 'suma-elementor' ),
'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
]
);

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

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

$this->end_controls_section();

// Query section
$this->start_controls_section(
'query_section',
[
'label' => __( 'Query', 'suma-elementor' ),
'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
]
);

$this->add_control(
'source',
[
'label' => __( 'Source', 'suma-elementor' ),
'type' => \Elementor\Controls_Manager::SELECT,
'default' => 'recent',
'options' => [
'recent' => 'Recent Products',
'featured' => 'Featured Products',
'sale' => 'On Sale',
'category' => 'By Category',
'manual' => 'Manual Selection',
],
]
);

$this->add_control(
'category',
[
'label' => __( 'Category', 'suma-elementor' ),
'type' => \Elementor\Controls_Manager::SELECT2,
'multiple' => true,
'options' => $this->get_product_categories(),
'condition' => [
'source' => 'category',
],
]
);

$this->end_controls_section();
}

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

$args = [
'post_type' => 'bigcommerce_product',
'posts_per_page' => $settings['products_per_page'],
];

// Apply source filter
switch ( $settings['source'] ) {
case 'featured':
$args['meta_query'] = [
[
'key' => 'bigcommerce_featured',
'value' => '1',
]
];
break;
case 'sale':
$args['meta_query'] = [
[
'key' => 'bigcommerce_sale_price',
'compare' => 'EXISTS',
]
];
break;
case 'category':
if ( ! empty( $settings['category'] ) ) {
$args['tax_query'] = [
[
'taxonomy' => 'bigcommerce_category',
'terms' => $settings['category'],
]
];
}
break;
}

$query = new \WP_Query( $args );

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

while ( $query->have_posts() ) : $query->the_post();
$this->render_product_card();
endwhile;

echo '</div>';

wp_reset_postdata();
endif;
}

protected function render_product_card() {
$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 );
$inventory = get_post_meta( $product_id, 'bigcommerce_inventory', true );

?>
<div class="product-card" data-product-id="<?php echo esc_attr( $bc_id ); ?>">
<div class="product-card__image">
<a href="<?php the_permalink(); ?>">
<?php the_post_thumbnail( 'medium_large' ); ?>
</a>

<?php if ( $sale_price ) : ?>
<span class="product-card__badge product-card__badge--sale">Sale</span>
<?php endif; ?>

<?php if ( $inventory <= 0 ) : ?>
<span class="product-card__badge product-card__badge--oos">Out of Stock</span>
<?php endif; ?>
</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 ) : ?>
<span class="price price--regular"><del><?php echo esc_html( '$' . $price ); ?></del></span>
<span class="price price--sale"><?php echo esc_html( '$' . $sale_price ); ?></span>
<?php else : ?>
<span class="price"><?php echo esc_html( '$' . $price ); ?></span>
<?php endif; ?>
</div>

<button class="button button--add-to-cart" data-product-id="<?php echo esc_attr( $bc_id ); ?>">
Add to Cart
</button>
</div>
</div>
<?php
}

protected function get_product_categories() {
$terms = get_terms( [
'taxonomy' => 'bigcommerce_category',
'hide_empty' => true,
] );

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

return $options;
}
}

Horizontal scrolling product carousel with touch support.

// elementor/widgets/product-carousel.php

namespace Suma_Elementor\Widgets;

class Product_Carousel extends \Elementor\Widget_Base {

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

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

public function get_script_depends() {
return [ 'swiper' ];
}

protected function register_controls() {
// Carousel settings
$this->start_controls_section(
'carousel_section',
[
'label' => __( 'Carousel', 'suma-elementor' ),
]
);

$this->add_control(
'slides_per_view',
[
'label' => __( 'Slides Per View', 'suma-elementor' ),
'type' => \Elementor\Controls_Manager::NUMBER,
'default' => 4,
'min' => 1,
'max' => 8,
]
);

$this->add_control(
'autoplay',
[
'label' => __( 'Autoplay', 'suma-elementor' ),
'type' => \Elementor\Controls_Manager::SWITCHER,
'default' => 'yes',
]
);

$this->add_control(
'loop',
[
'label' => __( 'Loop', 'suma-elementor' ),
'type' => \Elementor\Controls_Manager::SWITCHER,
'default' => 'yes',
]
);

$this->end_controls_section();
}

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

// Query products (simplified)
$products = get_posts( [
'post_type' => 'bigcommerce_product',
'posts_per_page' => 12,
] );

$carousel_options = [
'slidesPerView' => $settings['slides_per_view'],
'spaceBetween' => 30,
'loop' => 'yes' === $settings['loop'],
'autoplay' => 'yes' === $settings['autoplay'] ? [ 'delay' => 5000 ] : false,
'navigation' => [
'nextEl' => '.swiper-button-next',
'prevEl' => '.swiper-button-prev',
],
'breakpoints' => [
'320' => [ 'slidesPerView' => 1 ],
'640' => [ 'slidesPerView' => 2 ],
'1024' => [ 'slidesPerView' => 3 ],
'1280' => [ 'slidesPerView' => $settings['slides_per_view'] ],
]
];

?>
<div class="suma-product-carousel" data-carousel-options='<?php echo esc_attr( json_encode( $carousel_options ) ); ?>'>
<div class="swiper">
<div class="swiper-wrapper">
<?php foreach ( $products as $product ) : setup_postdata( $product ); ?>
<div class="swiper-slide">
<?php // Render product card ?>
</div>
<?php endforeach; wp_reset_postdata(); ?>
</div>
</div>
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
</div>
<?php
}
}

Content Widgets

Hero Banner Widget

Content Blocks Widget

Testimonials Widget

Video Player Widget

Call to Action Widget

Mega Menu Widget

Mobile Menu Widget

E-Commerce Specific Widgets

Category Grid Widget

Dealer Locator Widget

Back in Stock Form Widget

Gift Certificate Form Widget

Product Comparison Widget

Recently Viewed Products Widget

Build System (Laravel Mix)

Suma Elementor uses Laravel Mix for asset compilation with Tailwind CSS and modern JavaScript.

Laravel Mix Configuration

// webpack.mix.js

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

// Set public path
mix.setPublicPath('assets');

// Compile CSS with Tailwind
mix.postCss('resources/css/app.css', 'css', [
tailwindcss('./tailwind.config.js'),
])
.options({
processCssUrls: false,
postCss: [
require('autoprefixer'),
]
});

// Compile JavaScript
mix.js('resources/js/app.js', 'js')
.vue({ version: 3 }) // Optional: if using Vue components
.extract(); // Extract vendor libraries

// Copy images
mix.copyDirectory('resources/images', 'assets/images');

// Versioning for cache busting (production only)
if (mix.inProduction()) {
mix.version();
}

// Source maps (development only)
if (!mix.inProduction()) {
mix.sourceMaps();
}

// BrowserSync for live reload
mix.browserSync({
proxy: 'gomuddy.local',
files: [
'assets/**/*',
'**/*.php',
]
});

Tailwind CSS Configuration

// tailwind.config.js

module.exports = {
content: [
'./**/*.php',
'./resources/**/*.{js,jsx,vue}',
'./elementor/**/*.php',
],
theme: {
extend: {
colors: {
'muddy-green': {
50: '#f0f9f4',
100: '#dcf2e4',
200: '#bce5cd',
300: '#8dd2ad',
400: '#5cb887',
500: '#3a9d6a',
600: '#2a7e54',
700: '#236645',
800: '#1f5138',
900: '#1a432f',
},
'muddy-brown': {
50: '#faf7f5',
100: '#f3ede7',
200: '#e4d6c9',
300: '#d4bca6',
400: '#c19a7c',
500: '#b07f5f',
600: '#a36c53',
700: '#885846',
800: '#6f493d',
900: '#5b3d33',
},
},
fontFamily: {
'sans': ['Inter', 'system-ui', 'sans-serif'],
'heading': ['Oswald', 'sans-serif'],
},
spacing: {
'128': '32rem',
'144': '36rem',
},
borderRadius: {
'4xl': '2rem',
}
},
},
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/typography'),
require('@tailwindcss/aspect-ratio'),
],
}

JavaScript Entry Point

// resources/js/app.js

// Import dependencies
import Alpine from 'alpinejs';
import Swiper from 'swiper';
import { Navigation, Pagination, Autoplay } from 'swiper/modules';

// Import modules
import './modules/cart';
import './modules/product-quick-view';
import './modules/mobile-menu';
import './modules/search';

// Initialize Alpine.js
window.Alpine = Alpine;
Alpine.start();

// Initialize Swiper
document.addEventListener('DOMContentLoaded', () => {
const carousels = document.querySelectorAll('.suma-product-carousel');

carousels.forEach(carousel => {
const options = JSON.parse(carousel.dataset.carouselOptions || '{}');

new Swiper(carousel.querySelector('.swiper'), {
modules: [Navigation, Pagination, Autoplay],
...options
});
});
});

// Cart functionality
window.MuddyCart = {
addToCart(productId, variantId, quantity = 1) {
return fetch('/wp-json/bigcommerce/v1/cart/items', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
product_id: productId,
variant_id: variantId,
quantity: quantity
})
})
.then(response => response.json())
.then(data => {
// Update cart count in header
document.querySelector('.cart-count').textContent = data.item_count;

// Show success notification
this.showNotification('Product added to cart');

return data;
})
.catch(error => {
console.error('Cart error:', error);
this.showNotification('Error adding to cart', 'error');
});
},

showNotification(message, type = 'success') {
// Simple toast notification
const toast = document.createElement('div');
toast.className = `toast toast--${type}`;
toast.textContent = message;
document.body.appendChild(toast);

setTimeout(() => {
toast.classList.add('toast--show');
}, 10);

setTimeout(() => {
toast.classList.remove('toast--show');
setTimeout(() => toast.remove(), 300);
}, 3000);
}
};

Build Commands

// package.json (scripts section)
{
"scripts": {
"dev": "npm run development",
"development": "mix",
"watch": "mix watch",
"watch-poll": "mix watch -- --watch-options-poll=1000",
"hot": "mix watch --hot",
"prod": "npm run production",
"production": "mix --production"
},
"devDependencies": {
"laravel-mix": "^6.0.49",
"tailwindcss": "^3.4.0",
"autoprefixer": "^10.4.19",
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.12",
"@tailwindcss/aspect-ratio": "^0.4.2",
"alpinejs": "^3.13.0",
"swiper": "^11.1.0",
"axios": "^1.6.0"
}
}

Responsive Design

Breakpoint System

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

Mobile-First Approach

// Example: Responsive product grid
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
<!-- Product cards -->
</div>

Template Overrides

BigCommerce Templates

themes/suma-elementor/bigcommerce/
├── components/
│ ├── products/
│ │ ├── product-card.php
│ │ ├── product-title.php
│ │ ├── product-price.php
│ │ └── product-image.php
│ ├── cart/
│ │ ├── cart.php
│ │ ├── cart-item.php
│ │ └── cart-totals.php
│ └── checkout/
│ └── checkout-button.php
└── single-bigcommerce_product.php

Custom Product Card

// bigcommerce/components/products/product-card.php

$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 );
?>

<article class="product-card group">
<div class="product-card__image-wrapper relative overflow-hidden rounded-lg">
<a href="<?php the_permalink(); ?>" class="block">
<?php
the_post_thumbnail( 'large', [
'class' => 'w-full h-auto transform transition-transform duration-300 group-hover:scale-105'
] );
?>
</a>

<?php if ( $sale_price ) : ?>
<div class="absolute top-4 right-4 bg-red-600 text-white px-3 py-1 rounded-full text-sm font-semibold">
Save <?php echo absint( ( 1 - ( $sale_price / $price ) ) * 100 ); ?>%
</div>
<?php endif; ?>

<button class="absolute bottom-4 left-1/2 -translate-x-1/2 opacity-0 group-hover:opacity-100 transition-opacity bg-muddy-green-600 text-white px-6 py-2 rounded-full font-semibold hover:bg-muddy-green-700"
onclick="MuddyCart.addToCart(<?php echo esc_js( $bc_id ); ?>)">
Quick Add
</button>
</div>

<div class="product-card__content mt-4">
<h3 class="text-lg font-heading font-semibold text-gray-900 mb-2">
<a href="<?php the_permalink(); ?>" class="hover:text-muddy-green-600 transition-colors">
<?php the_title(); ?>
</a>
</h3>

<div class="flex items-center gap-2">
<?php if ( $sale_price ) : ?>
<span class="text-xl font-bold text-red-600">$<?php echo number_format( $sale_price, 2 ); ?></span>
<span class="text-sm text-gray-500 line-through">$<?php echo number_format( $price, 2 ); ?></span>
<?php else : ?>
<span class="text-xl font-bold text-gray-900">$<?php echo number_format( $price, 2 ); ?></span>
<?php endif; ?>
</div>
</div>
</article>

Performance Optimizations

Image Optimization

// functions.php

// Add WebP support
add_filter( 'wp_get_attachment_image_attributes', function( $attr, $attachment, $size ) {
if ( isset( $attr['src'] ) && function_exists( 'wp_get_attachment_image_srcset' ) ) {
$attr['loading'] = 'lazy';
$attr['decoding'] = 'async';
}
return $attr;
}, 10, 3 );

// Responsive image sizes
add_image_size( 'product-thumbnail', 300, 300, true );
add_image_size( 'product-medium', 600, 600, true );
add_image_size( 'product-large', 1200, 1200, true );

Critical CSS

// Load critical CSS inline
add_action( 'wp_head', function() {
if ( is_front_page() ) {
echo '<style>' . file_get_contents( get_template_directory() . '/assets/css/critical.css' ) . '</style>';
}
}, 1 );

Lazy Loading

// Use Intersection Observer for lazy loading
const lazyImages = document.querySelectorAll('img[loading="lazy"]');

if ('IntersectionObserver' in window) {
const imageObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.removeAttribute('data-src');
imageObserver.unobserve(img);
}
});
});

lazyImages.forEach(img => imageObserver.observe(img));
}

Customization Guide

Adding a Custom Widget

  1. Create widget class in elementor/widgets/my-widget.php
  2. Register widget in functions.php:
add_action( 'elementor/widgets/register', function( $widgets_manager ) {
require_once get_template_directory() . '/elementor/widgets/my-widget.php';
$widgets_manager->register( new \Suma_Elementor\Widgets\My_Widget() );
} );

Adding Custom CSS

// functions.php
add_action( 'wp_enqueue_scripts', function() {
wp_enqueue_style(
'muddy-custom',
get_template_directory_uri() . '/assets/css/custom.css',
[ 'suma-elementor-app' ],
wp_get_theme()->get( 'Version' )
);
}, 20 );

Theme Hooks

// Available theme hooks
do_action( 'suma_before_header' );
do_action( 'suma_header_start' );
do_action( 'suma_header_end' );
do_action( 'suma_after_header' );

do_action( 'suma_before_content' );
do_action( 'suma_content_start' );
do_action( 'suma_content_end' );
do_action( 'suma_after_content' );

do_action( 'suma_before_footer' );
do_action( 'suma_footer_start' );
do_action( 'suma_footer_end' );
do_action( 'suma_after_footer' );