
This module is designed to visually showcase the features of a product or service by arranging images and text in an alternating layout. It uses scroll-triggered animations to engage users.


"type": "group" with the "occurrence" property in the fields.json."image_on_left" within the group, a HubL if statement outputs a specific CSS class (e.g., layout--image-left) to the HTML, which controls the layout.IntersectionObserver API is required. It detects when an element enters the viewport and adds a CSS class (e.g., .is-visible) to trigger the CSS transition.fields.json)This module is configured by the following field definitions.
[
{
"name": "boxes",
"label": "Content Boxes",
"type": "group",
"occurrence": {
"min": 1,
"max": null,
"default": 2
},
"children": [
{
"type": "image",
"name": "image",
"label": "Image",
"responsive": true,
"show_loading": false
},
{
"type": "text",
"name": "section_number",
"label": "Section Number"
},
{
"type": "text",
"name": "subtitle",
"label": "Subtitle"
},
{
"type": "text",
"name": "title",
"label": "Title"
},
{
"type": "richtext",
"name": "description",
"label": "Description"
},
{
"type": "boolean",
"name": "image_on_left",
"label": "Place image on the left",
"display": "checkbox",
"default": false
}
]
},
{
"type": "color",
"name": "title_color",
"label": "Title Color",
"tab": "STYLE"
},
{
"type": "color",
"name": "subtitle_color",
"label": "Subtitle Color",
"tab": "STYLE"
},
{
"type": "color",
"name": "description_color",
"label": "Description Color",
"tab": "STYLE"
},
{
"type": "number",
"name": "title_font_size",
"label": "Title Font Size",
"suffix": "px",
"tab": "STYLE"
},
{
"type": "number",
"name": "subtitle_font_size",
"label": "Subtitle Font Size",
"suffix": "px",
"tab": "STYLE"
},
{
"type": "number",
"name": "description_font_size",
"label": "Description Font Size",
"suffix": "px",
"tab": "STYLE"
},
{
"type": "number",
"name": "section_number_font_size",
"label": "Section Number Font Size",
"suffix": "px",
"tab": "STYLE"
}
]
{# ======== module.html (with font size adjustment feature) ======== #}
<div class="feature-section-module">
{% for box in module.boxes %}
{% set layout_class = box.image_on_left ? 'layout--image-left' : 'layout--image-right' %}
<div class="feature-box animated-box {{ layout_class }}">
<div class="feature-text-content">
<div class="section-header">
{# ★ Change: Added font-size #}
<span class="section-number" style="color: {{ module.title_color.color }}; {% if module.section_number_font_size %}font-size: {{ module.section_number_font_size }}px;{% endif %}">
{{ box.section_number }}
</span>
{# ★ Change: Added font-size #}
<span class="section-subtitle" style="color: {{ module.subtitle_color.color }}; {% if module.subtitle_font_size %}font-size: {{ module.subtitle_font_size }}px;{% endif %}">
{{ box.subtitle }}
</span>
</div>
{# ★ Change: Added font-size #}
<h2 class="section-title" style="color: {{ module.title_color.color }}; {% if module.title_font_size %}font-size: {{ module.title_font_size }}px;{% endif %}">
{{ box.title }}
</h2>
{# ★ Change: Added font-size #}
<div class="section-description" style="color: {{ module.description_color.color }}; {% if module.description_font_size %}font-size: {{ module.description_font_size }}px;{% endif %}">
{{ box.description }}
</div>
</div>
<div class="feature-image-content">
{% if box.image.src %}
<img src="{{ box.image.src }}" alt="{{ box.image.alt }}" loading="lazy">
{% endif %}
</div>
</div>
{% endfor %}
</div>
/* ======== module.css (Font-size adjusted version) ======== */
/* Basic styles for each section (box) */
.feature-box {
display: flex;
align-items: center;
gap: 60px;
margin-bottom: 80px;
opacity: 0;
transform: translateY(40px);
transition: opacity 0.8s ease-out, transform 0.8s ease-out;
}
.feature-box.is-visible {
opacity: 1;
transform: translateY(0);
}
.feature-text-content,
.feature-image-content {
flex: 1;
min-width: 0;
}
.feature-box.layout--image-left {
flex-direction: row-reverse;
}
.feature-image-content img {
width: 100%;
height: auto;
border-radius: 8px;
}
/* Text-related styles */
.section-header {
display: flex;
align-items: baseline;
margin-bottom: 16px;
}
.section-number {
font-size: 60px; /* ★ Change: Slightly smaller from 72px */
font-weight: 700;
line-height: 1;
margin-right: 16px;
}
.section-subtitle {
font-size: 16px; /* ★ Change: Slightly smaller from 18px */
font-weight: 600;
}
.section-title {
font-size: 32px; /* ★ Change: Slightly smaller from 36px */
font-weight: 700;
line-height: 1.4;
margin-bottom: 20px;
}
.section-description {
font-size: 16px; /* No change */
line-height: 1.8;
}
/* Responsive settings for smartphones */
@media (max-width: 768px) {
.feature-box {
flex-direction: column !important;
gap: 30px;
margin-bottom: 50px;
}
.section-title {
font-size: 26px; /* ★ Change: Adjusted size for smartphones */
}
.section-number {
font-size: 50px; /* ★ Change: Adjusted size for smartphones */
}
}
// ======== module.js ======== //
document.addEventListener('DOMContentLoaded', () => {
// Get all elements to be animated
const animatedBoxes = document.querySelectorAll('.animated-box');
// Create an observer to watch if elements become visible
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry, index) => {
// If entry.isIntersecting is true, it means the element has entered the viewport
if (entry.isIntersecting) {
// Use setTimeout to create a staggered animation effect
setTimeout(() => {
entry.target.classList.add('is-visible');
}, index * 200); // Delay each item by 0.2 seconds
// Stop observing the element once it has been made visible
observer.unobserve(entry.target);
}
});
}, {
threshold: 0.1 // Trigger when 10% of the element is visible
});
// Start observing each box
animatedBoxes.forEach(box => {
observer.observe(box);
});
});
As explained in the "Image Optimization" section, to maintain the design quality of the entire slider, it is important to unify the height of all logo images (e.g., to 60px). Using transparent background PNG images is also recommended.
No, not necessarily. As explained in the "Implementation Points," there are two potential ways to implement this: Using CSS animations only, which does not require a library. Using a JavaScript library (like Swiper.js) if more advanced features like pausing or drag controls are needed. It depends on how the module was built.
Yes, you can. As noted in the "What you can do" section, you can optionally set a link for each logo. The field configuration is set up as a repeating "Group Field" that includes an Image field (for the logo) and a URL field (for the link) as a set.
We can customize this sample to match your specific business requirements.
Book Free Consultation