Logo
Home
Resources

Product

Custom Workflow

Resources

Blog
Youtube
Template
Home
>
Product
>
Template Top
>
detail

[Hubspot modules] Feature Section Module

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.

‍

Demo Video

Over view
Code

Detail

Purpose

  • To compellingly communicate product features and benefits in a storytelling format.
  • To present case studies or customer testimonials persuasively with accompanying photos.
  • To explain a service delivery process or workflow step-by-step.

Features

  • Ability to add, delete, and reorder an unlimited number of "image" and "heading/description" sets.
  • Option to select the image position (left or right) for each individual set.
  • Full control over the text content, color, and font size for the section number, subtitle, title, and description.
  • A fade-in-up animation is applied to elements as they enter the viewport upon scrolling.

Implementation Points & Notes

  • Repeater: The ability to add multiple content boxes is implemented using "type": "group" with the "occurrence" property in the fields.json.
  • Layout Control: Based on the value of the Boolean field named "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.
  • Animation: A dedicated JavaScript file using the 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.
  • Styling Options: Global style settings (like text colors and font sizes) are handled by color picker and number fields located in the "STYLE" tab, which apply their values to inline styles in the HTML.

Field Definitions (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"
  }
]

‍

Source Code

HTML
{# ======== 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>

‍

CSS
/* ======== 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 */
  }
}

‍

‍

‍

Javascript
// ======== 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);
  });
});

‍

‍

‍

‍

FAQ

I added logos, but their sizes are inconsistent and they aren't aligned vertically. How can I make them look uniform?

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.

Does this module require an external JavaScript library like Swiper.js to work (i.e., to scroll)?

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.

Can I set a link on each logo image to that company's website?

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.

Search

Search more

Related Template

Need Customization?

We can customize this sample to match your specific business requirements.

Book Free Consultation

HubSpot Custom Workflow Guide: How to Auto-Search and Recommend Marketing Events

HubSpot Custom Workflow Guide: How to Auto-Recommend Files with Generative AI

HubSpot Custom Workflow Guide: How to "Search" for Marketing Campaigns and Use Their Data

HubSpot Custom Workflow Guide: How to "Search" for Tickets and Auto-Associate Contacts

HubSpot Custom Workflow Guide: How to "Search" for Deals and Auto-Associate Contacts

Company Info
Name : SweetsVillage .Inc
CEO :
‍
Tomoo Motoyama

HomeTemplateCustomWorkflow
Terms & ConditionsPrivacy PolicyContact us

Copyright ©SweetsVillage .Inc

Back To Top Image