Skip to main content

Theme Customizations

ScentLok uses the Suma Elementor theme, a custom-built WordPress theme with extensive Elementor integration and modern build tooling.

Overview

Theme: Suma Elementor
Version: 3.0.0
Builder: Elementor Pro 3.26.0
Build System: Laravel Mix 6.0
CSS Framework: Tailwind CSS 3.4

Theme Structure

suma-elementor/
├── assets/
│ ├── src/
│ │ ├── css/
│ │ │ ├── app.css # Main styles
│ │ │ ├── tailwind.css # Tailwind entry
│ │ │ └── components/ # Component styles
│ │ └── js/
│ │ ├── app.js # Main JavaScript
│ │ ├── components/ # Preact components
│ │ └── utils/ # Helper functions
│ └── dist/ # Compiled assets
├── elementor/
│ ├── widgets/ # Custom widgets
│ ├── controls/ # Custom controls
│ └── extensions/ # Widget extensions
├── inc/
│ ├── classes/ # PHP classes
│ ├── hooks/ # Action/filter hooks
│ └── helpers.php # Helper functions
├── templates/
│ ├── header.php
│ ├── footer.php
│ └── parts/ # Template parts
├── woocommerce/ # WooCommerce overrides
├── webpack.mix.js # Laravel Mix config
├── tailwind.config.js # Tailwind config
├── package.json
└── style.css # Theme header

Custom Elementor Widgets

Product Grid Widget

Displays BigCommerce products in a grid layout:

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';
}

protected function register_controls() {
$this->start_controls_section(
'content_section',
[
'label' => 'Content',
'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
]
);

$this->add_control(
'product_source',
[
'label' => 'Product Source',
'type' => \Elementor\Controls_Manager::SELECT,
'options' => [
'recent' => 'Recent Products',
'featured' => 'Featured Products',
'category' => 'By Category',
'manual' => 'Manual Selection',
],
'default' => 'recent',
]
);

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

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

$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'],
];

if ( 'featured' === $settings['product_source'] ) {
$args['meta_query'] = [
[
'key' => 'bc_is_featured',
'value' => true,
],
];
}

$products = new \WP_Query( $args );

if ( $products->have_posts() ) :
?>
<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( get_the_ID() );
endwhile;
?>
</div>
<?php
endif;

wp_reset_postdata();
}

private function render_product_card( $post_id ) {
$bc_id = get_post_meta( $post_id, 'bc_id', true );
$price = get_post_meta( $post_id, 'bc_price', true );
$on_sale = get_post_meta( $post_id, 'bc_sale_price', true ) ? true : false;
?>
<div class="product-card bg-white rounded-lg shadow-md overflow-hidden hover:shadow-xl transition-shadow">
<a href="<?php echo esc_url( get_permalink() ); ?>" class="block">
<?php if ( has_post_thumbnail() ) : ?>
<div class="product-image aspect-square">
<?php the_post_thumbnail( 'medium', [ 'class' => 'w-full h-full object-cover' ] ); ?>
</div>
<?php endif; ?>

<div class="product-info p-4">
<h3 class="product-title text-lg font-semibold mb-2">
<?php echo esc_html( get_the_title() ); ?>
</h3>

<div class="product-price text-xl font-bold text-primary">
<?php if ( $on_sale ) : ?>
<span class="line-through text-gray-500 mr-2">
$<?php echo esc_html( get_post_meta( $post_id, 'bc_price', true ) ); ?>
</span>
<span class="text-red-600">
$<?php echo esc_html( get_post_meta( $post_id, 'bc_sale_price', true ) ); ?>
</span>
<?php else : ?>
$<?php echo esc_html( $price ); ?>
<?php endif; ?>
</div>
</div>
</a>
</div>
<?php
}
}

Rotating product showcase:

class Product_Carousel extends \Elementor\Widget_Base {

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

protected function render() {
$settings = $this->get_settings_for_display();
$products = $this->get_products( $settings );
?>
<div class="swiper product-carousel" data-swiper='<?php echo json_encode( $this->get_swiper_config( $settings ) ); ?>'>
<div class="swiper-wrapper">
<?php foreach ( $products as $product ) : ?>
<div class="swiper-slide">
<?php $this->render_product_card( $product->ID ); ?>
</div>
<?php endforeach; ?>
</div>

<div class="swiper-pagination"></div>
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
</div>
<?php
}

private function get_swiper_config( $settings ) {
return [
'slidesPerView' => 1,
'spaceBetween' => 20,
'loop' => true,
'autoplay' => [
'delay' => 5000,
],
'pagination' => [
'clickable' => true,
],
'navigation' => [
'nextEl' => '.swiper-button-next',
'prevEl' => '.swiper-button-prev',
],
'breakpoints' => [
640 => [ 'slidesPerView' => 2 ],
1024 => [ 'slidesPerView' => 3 ],
1280 => [ 'slidesPerView' => 4 ],
],
];
}
}

SLoyalty Widget

Customer loyalty points display:

class SLoyalty_Widget extends \Elementor\Widget_Base {

public function get_name() {
return 'sloyalty-widget';
}

public function get_title() {
return 'SLoyalty Points';
}

protected function render() {
if ( ! is_user_logged_in() ) {
return;
}

$customer_email = wp_get_current_user()->user_email;
$balance = $this->get_sloyalty_balance( $customer_email );
?>
<div class="sloyalty-widget bg-gradient-to-r from-primary to-secondary p-6 rounded-lg text-white">
<div class="flex items-center justify-between">
<div>
<p class="text-sm opacity-90">Your Rewards</p>
<p class="text-3xl font-bold"><?php echo esc_html( number_format( $balance ) ); ?> Points</p>
</div>
<div>
<a href="<?php echo esc_url( home_url( '/account/rewards' ) ); ?>"
class="bg-white text-primary px-4 py-2 rounded-md font-semibold hover:bg-gray-100">
View Rewards
</a>
</div>
</div>
</div>
<?php
}

private function get_sloyalty_balance( $email ) {
$cache_key = 'sloyalty_balance_' . md5( $email );
$balance = wp_cache_get( $cache_key );

if ( false === $balance ) {
$api_key = 'e6838c7d';
$url = "https://api.sloyalty.com/v1/customers/{$email}/balance";

$response = wp_remote_get( $url, [
'headers' => [
'Authorization' => "Bearer {$api_key}",
],
]);

if ( ! is_wp_error( $response ) ) {
$data = json_decode( wp_remote_retrieve_body( $response ), true );
$balance = $data['points_balance'] ?? 0;

wp_cache_set( $cache_key, $balance, '', 5 * MINUTE_IN_SECONDS );
}
}

return $balance;
}
}

Additional Custom Widgets

The theme includes 60+ custom Elementor widgets:

  • Product Widgets: Grid, Carousel, Featured, Categories, Brands
  • Content Widgets: Hero Section, Feature Blocks, Testimonials
  • Commerce Widgets: Cart, Checkout Summary, Order Tracking
  • Interactive: Search Bar, Filters, Sort Options
  • Marketing: Newsletter Signup, Promo Banners, Countdown Timer

Preact Components

Add to Cart Component

// assets/src/js/components/AddToCart.jsx
import { h, Component } from 'preact';
import { useState } from 'preact/hooks';

export default function AddToCart({ productId, variantId, price }) {
const [quantity, setQuantity] = useState(1);
const [loading, setLoading] = useState(false);
const [added, setAdded] = useState(false);

const handleAddToCart = async () => {
setLoading(true);

try {
const response = await fetch('/wp-json/bigcommerce/v1/cart/add', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
product_id: productId,
variant_id: variantId,
quantity: quantity,
}),
});

if (response.ok) {
setAdded(true);

// Update cart count
document.dispatchEvent(new CustomEvent('cartUpdated'));

setTimeout(() => setAdded(false), 3000);
}
} catch (error) {
console.error('Add to cart error:', error);
} finally {
setLoading(false);
}
};

return (
<div class="add-to-cart flex items-center gap-4">
<div class="quantity-selector flex items-center border rounded-md">
<button
class="px-3 py-2"
onClick={() => setQuantity(Math.max(1, quantity - 1))}
disabled={loading}
>

</button>
<input
type="number"
value={quantity}
class="w-16 text-center border-x"
min="1"
onChange={(e) => setQuantity(parseInt(e.target.value) || 1)}
/>
<button
class="px-3 py-2"
onClick={() => setQuantity(quantity + 1)}
disabled={loading}
>
+
</button>
</div>

<button
class={`btn-primary px-6 py-3 rounded-md font-semibold ${loading ? 'opacity-50' : ''}`}
onClick={handleAddToCart}
disabled={loading}
>
{added ? '✓ Added!' : loading ? 'Adding...' : `Add to Cart - $${price}`}
</button>
</div>
);
}

Variant Selector Component

// assets/src/js/components/VariantSelector.jsx
import { h } from 'preact';
import { useState, useEffect } from 'preact/hooks';

export default function VariantSelector({ productId, onVariantChange }) {
const [variants, setVariants] = useState([]);
const [selectedOptions, setSelectedOptions] = useState({});
const [currentVariant, setCurrentVariant] = useState(null);

useEffect(() => {
fetchVariants();
}, [productId]);

const fetchVariants = async () => {
const response = await fetch(`/wp-json/bigcommerce/v1/products/${productId}/variants`);
const data = await response.json();
setVariants(data.variants);
};

const handleOptionChange = (optionName, value) => {
const newOptions = { ...selectedOptions, [optionName]: value };
setSelectedOptions(newOptions);

// Find matching variant
const variant = variants.find(v =>
Object.entries(newOptions).every(([key, val]) =>
v.option_values.some(ov => ov.option_display_name === key && ov.label === val)
)
);

setCurrentVariant(variant);
onVariantChange(variant);
};

// Group variants by option
const optionGroups = {};
variants.forEach(variant => {
variant.option_values.forEach(option => {
if (!optionGroups[option.option_display_name]) {
optionGroups[option.option_display_name] = new Set();
}
optionGroups[option.option_display_name].add(option.label);
});
});

return (
<div class="variant-selector space-y-4">
{Object.entries(optionGroups).map(([optionName, values]) => (
<div key={optionName} class="option-group">
<label class="block font-semibold mb-2">{optionName}</label>
<div class="flex flex-wrap gap-2">
{[...values].map(value => (
<button
key={value}
class={`px-4 py-2 border rounded-md ${
selectedOptions[optionName] === value
? 'border-primary bg-primary text-white'
: 'border-gray-300 hover:border-primary'
}`}
onClick={() => handleOptionChange(optionName, value)}
>
{value}
</button>
))}
</div>
</div>
))}
</div>
);
}

Build System

Laravel Mix Configuration

// webpack.mix.js
const mix = require('laravel-mix');

mix
.setPublicPath('assets/dist')
.js('assets/src/js/app.js', 'assets/dist/js')
.postCss('assets/src/css/app.css', 'assets/dist/css', [
require('tailwindcss'),
require('autoprefixer'),
])
.react() // Enable Preact/React JSX
.sourceMaps()
.version();

// Configure Webpack
mix.webpackConfig({
resolve: {
alias: {
'react': 'preact/compat',
'react-dom': 'preact/compat',
},
},
externals: {
jquery: 'jQuery',
},
});

// Production optimizations
if (mix.inProduction()) {
mix.options({
terser: {
terserOptions: {
compress: {
drop_console: true,
},
},
},
});
}

Tailwind Configuration

// tailwind.config.js
module.exports = {
content: [
'./**/*.php',
'./assets/src/**/*.{js,jsx}',
'./elementor/**/*.php',
],
theme: {
extend: {
colors: {
primary: {
DEFAULT: '#2D5016',
light: '#4A7C2C',
dark: '#1A3C0E',
},
secondary: {
DEFAULT: '#8B4513',
light: '#A0522D',
dark: '#654321',
},
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
heading: ['Montserrat', 'sans-serif'],
},
},
},
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/typography'),
require('@tailwindcss/aspect-ratio'),
],
};

NPM Scripts

{
"scripts": {
"dev": "mix",
"watch": "mix watch",
"hot": "mix watch --hot",
"production": "mix --production"
},
"devDependencies": {
"laravel-mix": "^6.0.49",
"tailwindcss": "^3.4.0",
"autoprefixer": "^10.4.14",
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/typography": "^0.5.9",
"preact": "^10.19.3"
}
}

Responsive Design

Breakpoints

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

/* Custom responsive utilities */
.container {
@apply max-w-7xl mx-auto px-4 sm:px-6 lg:px-8;
}

.section-padding {
@apply py-12 md:py-16 lg:py-24;
}

.grid-responsive {
@apply grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6;
}

Performance Optimizations

Lazy Loading Images

// Automatic lazy loading
add_filter( 'wp_get_attachment_image_attributes', function( $attr ) {
$attr['loading'] = 'lazy';
return $attr;
});

Code Splitting

Assets are automatically split by Laravel Mix for optimal loading.

Critical CSS

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

Hooks & Filters

Theme Customization Hooks

// Modify product grid columns
add_filter( 'suma/product_grid/columns', function( $columns ) {
return 3; // Override default
});

// Add custom data to product cards
add_action( 'suma/product_card/after_title', function( $post_id ) {
echo '<div class="custom-badge">New</div>';
});

// Customize SLoyalty widget display
add_filter( 'suma/sloyalty/widget_template', function( $template ) {
return get_stylesheet_directory() . '/templates/sloyalty-custom.php';
});