AI Tool Thunderdome: Two Tools Enter, One Tool Wins

THE CHALLENGE: Pick two competing AI tools you’ve used for the same task (text summarization, image creation, data analysis, etc.). Put them head-to-head by giving them identical inputs and comparing their outputs.

Share screenshots of both results, your evaluation criteria, and crown your winner! This real-world comparison will help fellow community members make informed decisions about their AI toolkit.

Hey @DanielleGriffin, @Sayed1, @Akent1 and @VBright2025 ! Who’s ready to put their AI tools to the test? Show us your AI Tool Thunderdome results! :backhand_index_pointing_down:

Love this!
I’ve challenged ChatGPT(Free) and Cursor for creating something I’d call a common HubSpot module. I’ve choosen this on purpose because HubL isn’t that common compared to something as React, but the majority of HubSpot pages are build upon it.

The prompt:

Help me create a HubSpot module with a headline, subline, an image repeater and an optional button above the image repeater. The repeater should be used to display an image slider with the help of splide js.
Furthermore the user should have the options to style many aspects of module like colors, border-radius of the images and button styles.
The module should pass current accessibility features like as screen-reader texts, semantic HTML tags.

To compare both of them as best as possible I did not use any MCP or rules (like cursorrules). Also I didn’t want to spend much time in “prompt engineering” as I assume many people will just write something right away and also might not have a developers knowledge. This is why the prompt is on the low-end of a “good prompt” and also has a few errors.

Before switching to ChatGPT I’ve tried Replit, but it failed as Replit told me that it’s not capable of creating standalone modules since it’s focusing on app creation. Bummer.

Cursor

Cursor did way better job but also made mistakes.

It started with the module.json (which is the meta.json)

Spoiler

{
 "label": "Image Slider Module",
 "css_assets": [],
 "external_js": [
 "https://cdn.jsdelivr.net/npm/@splidejs/splide@4.1.4/dist/js/splide.min.js"
 ],
 "external_css": [
 "https://cdn.jsdelivr.net/npm/@splidejs/splide@4.1.4/dist/css/splide.min.css"
 ],
 "global": false,
 "help_text": "A customizable image slider module with headline, subline, and optional button. Uses Splide.js for smooth sliding functionality.",
 "host_template_types": [
 "PAGE",
 "BLOG_POST",
 "BLOG_LISTING"
 ],
 "icon": "https://cdn2.hubspot.net/hubfs/6326501/boilerplate/Module%20Icons/image-gallery.svg",
 "js_assets": [],
 "other_assets": [],
 "smart_type": "NOT_SMART",
 "tags": [],
 "is_available_for_new_content": true
}

procceeded with the fields.json

Spoiler

[
 {
 "name": "headline",
 "label": "Headline",
 "required": false,
 "type": "text",
 "default": "Image Slider",
 "show_emoji_picker": false,
 "help_text": "Enter the main headline for the image slider section"
 },
 {
 "name": "subline",
 "label": "Subline",
 "required": false,
 "type": "text",
 "default": "Browse through our collection of images",
 "show_emoji_picker": false,
 "help_text": "Enter a subtitle or description for the image slider"
 },
 {
 "name": "show_button",
 "label": "Show Button",
 "required": false,
 "type": "boolean",
 "default": false,
 "help_text": "Toggle to show/hide the button above the image slider"
 },
 {
 "name": "button_text",
 "label": "Button Text",
 "required": false,
 "type": "text",
 "default": "Learn More",
 "show_emoji_picker": false,
 "help_text": "Text to display on the button",
 "visibility": {
 "controlling_field": "show_button",
 "controlling_value": true,
 "operator": "EQUAL"
 }
 },
 {
 "name": "button_link",
 "label": "Button Link",
 "required": false,
 "type": "link",
 "help_text": "Select where the button should link to",
 "visibility": {
 "controlling_field": "show_button",
 "controlling_value": true,
 "operator": "EQUAL"
 }
 },
 {
 "name": "images",
 "label": "Images",
 "required": true,
 "type": "repeater",
 "help_text": "Add images to the slider. Each image can have a title and description for accessibility.",
 "field_list": [
 {
 "name": "image",
 "label": "Image",
 "required": true,
 "type": "image",
 "help_text": "Select an image for the slider"
 },
 {
 "name": "image_title",
 "label": "Image Title",
 "required": false,
 "type": "text",
 "help_text": "Title for the image (used for accessibility and alt text)"
 },
 {
 "name": "image_description",
 "label": "Image Description",
 "required": false,
 "type": "text",
 "help_text": "Description of the image (used for accessibility)"
 }
 ]
 },
 {
 "name": "slider_options",
 "label": "Slider Options",
 "type": "group",
 "help_text": "Configure the slider behavior and appearance",
 "children": [
 {
 "name": "autoplay",
 "label": "Autoplay",
 "required": false,
 "type": "boolean",
 "default": true,
 "help_text": "Enable automatic sliding"
 },
 {
 "name": "autoplay_speed",
 "label": "Autoplay Speed (seconds)",
 "required": false,
 "type": "number",
 "default": 3,
 "min": 1,
 "max": 10,
 "help_text": "Time between automatic slides",
 "visibility": {
 "controlling_field": "autoplay",
 "controlling_value": true,
 "operator": "EQUAL"
 }
 },
 {
 "name": "show_arrows",
 "label": "Show Navigation Arrows",
 "required": false,
 "type": "boolean",
 "default": true,
 "help_text": "Display previous/next arrow buttons"
 },
 {
 "name": "show_dots",
 "label": "Show Dot Indicators",
 "required": false,
 "type": "boolean",
 "default": true,
 "help_text": "Display dot indicators for navigation"
 },
 {
 "name": "slides_per_view",
 "label": "Slides Per View",
 "required": false,
 "type": "choice",
 "choices": [
 ["1", "1"],
 ["2", "2"],
 ["3", "3"],
 ["4", "4"]
 ],
 "default": "1",
 "help_text": "Number of slides visible at once"
 }
 ]
 },
 {
 "name": "styling",
 "label": "Styling Options",
 "type": "group",
 "help_text": "Customize the appearance of the module",
 "children": [
 {
 "name": "headline_color",
 "label": "Headline Color",
 "required": false,
 "type": "color",
 "default": "#333333",
 "help_text": "Color for the headline text"
 },
 {
 "name": "subline_color",
 "label": "Subline Color",
 "required": false,
 "type": "color",
 "default": "#666666",
 "help_text": "Color for the subline text"
 },
 {
 "name": "image_border_radius",
 "label": "Image Border Radius",
 "required": false,
 "type": "choice",
 "choices": [
 ["0", "None"],
 ["4px", "Small"],
 ["8px", "Medium"],
 ["12px", "Large"],
 ["16px", "Extra Large"],
 ["50%", "Circular"]
 ],
 "default": "8px",
 "help_text": "Border radius for the images"
 },
 {
 "name": "button_style",
 "label": "Button Style",
 "required": false,
 "type": "choice",
 "choices": [
 ["primary", "Primary"],
 ["secondary", "Secondary"],
 ["outline", "Outline"],
 ["ghost", "Ghost"]
 ],
 "default": "primary",
 "help_text": "Style variant for the button",
 "visibility": {
 "controlling_field": "show_button",
 "controlling_value": true,
 "operator": "EQUAL"
 }
 },
 {
 "name": "button_color",
 "label": "Button Color",
 "required": false,
 "type": "color",
 "default": "#007cba",
 "help_text": "Background color for the button",
 "visibility": {
 "controlling_field": "show_button",
 "controlling_value": true,
 "operator": "EQUAL"
 }
 },
 {
 "name": "button_text_color",
 "label": "Button Text Color",
 "required": false,
 "type": "color",
 "default": "#ffffff",
 "help_text": "Text color for the button",
 "visibility": {
 "controlling_field": "show_button",
 "controlling_value": true,
 "operator": "EQUAL"
 }
 },
 {
 "name": "arrow_color",
 "label": "Arrow Color",
 "required": false,
 "type": "color",
 "default": "#333333",
 "help_text": "Color for navigation arrows",
 "visibility": {
 "controlling_field": "show_arrows",
 "controlling_value": true,
 "operator": "EQUAL"
 }
 },
 {
 "name": "dot_color",
 "label": "Dot Color",
 "required": false,
 "type": "color",
 "default": "#007cba",
 "help_text": "Color for dot indicators",
 "visibility": {
 "controlling_field": "show_dots",
 "controlling_value": true,
 "operator": "EQUAL"
 }
 }
 ]
 },
 {
 "name": "accessibility",
 "label": "Accessibility",
 "type": "group",
 "help_text": "Accessibility settings for screen readers and keyboard navigation",
 "children": [
 {
 "name": "slider_label",
 "label": "Slider Label",
 "required": false,
 "type": "text",
 "default": "Image slider",
 "help_text": "Label for screen readers to identify the slider"
 },
 {
 "name": "previous_button_label",
 "label": "Previous Button Label",
 "required": false,
 "type": "text",
 "default": "Previous image",
 "help_text": "Label for the previous button (screen readers)",
 "visibility": {
 "controlling_field": "show_arrows",
 "controlling_value": true,
 "operator": "EQUAL"
 }
 },
 {
 "name": "next_button_label",
 "label": "Next Button Label",
 "required": false,
 "type": "text",
 "default": "Next image",
 "help_text": "Label for the next button (screen readers)",
 "visibility": {
 "controlling_field": "show_arrows",
 "controlling_value": true,
 "operator": "EQUAL"
 }
 }
 ]
 }
]

wrote a module.html

Spoiler

{% if module.headline or module.subline or module.images %}
<section class="image-slider-module" 
 {% if module.accessibility.slider_label %}aria-label="{{ module.accessibility.slider_label }}"{% endif %}>

 <!-- Header Section -->
 {% if module.headline or module.subline %}
 <header class="image-slider-header">
 {% if module.headline %}
 <h2 class="image-slider-headline" 
 style="color: {{ module.styling.headline_color }};">
 {{ module.headline }}
 </h2>
 {% endif %}

 {% if module.subline %}
 <p class="image-slider-subline" 
 style="color: {{ module.styling.subline_color }};">
 {{ module.subline }}
 </p>
 {% endif %}
 </header>
 {% endif %}

 <!-- Optional Button -->
 {% if module.show_button and module.button_text %}
 <div class="image-slider-button-container">
 {% if module.button_link %}
 <a href="{{ module.button_link.url }}" 
 class="image-slider-button image-slider-button--{{ module.styling.button_style }}"
 style="background-color: {{ module.styling.button_color }}; color: {{ module.styling.button_text_color }};"
 {% if module.button_link.open_in_new_tab %}target="_blank"{% endif %}
 {% if module.button_link.no_follow %}rel="nofollow"{% endif %}>
 {{ module.button_text }}
 </a>
 {% else %}
 <button class="image-slider-button image-slider-button--{{ module.styling.button_style }}"
 style="background-color: {{ module.styling.button_color }}; color: {{ module.styling.button_text_color }};">
 {{ module.button_text }}
 </button>
 {% endif %}
 </div>
 {% endif %}

 <!-- Image Slider -->
 {% if module.images %}
 <div class="image-slider-container">
 <div class="splide image-slider" 
 role="region" 
 aria-label="{{ module.accessibility.slider_label|default('Image slider') }}">

 <div class="splide__track">
 <ul class="splide__list">
 {% for image_item in module.images %}
 <li class="splide__slide">
 <figure class="image-slider-figure">
 <img src="{{ image_item.image.src }}" 
 alt="{{ image_item.image_title|default(image_item.image.alt)|default('Slider image') }}"
 class="image-slider-image"
 style="border-radius: {{ module.styling.image_border_radius }};"
 width="{{ image_item.image.width }}"
 height="{{ image_item.image.height }}"
 loading="lazy">

 {% if image_item.image_title or image_item.image_description %}
 <figcaption class="image-slider-caption">
 {% if image_item.image_title %}
 <h3 class="image-slider-caption-title">{{ image_item.image_title }}</h3>
 {% endif %}
 {% if image_item.image_description %}
 <p class="image-slider-caption-description">{{ image_item.image_description }}</p>
 {% endif %}
 </figcaption>
 {% endif %}
 </figure>
 </li>
 {% endfor %}
 </ul>
 </div>

 <!-- Navigation Arrows -->
 {% if module.slider_options.show_arrows %}
 <div class="splide__arrows">
 <button class="splide__arrow splide__arrow--prev" 
 type="button"
 aria-label="{{ module.accessibility.previous_button_label|default('Previous image') }}"
 style="color: {{ module.styling.arrow_color }};">
 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" width="40" height="40" focusable="false">
 <path d="m15.5 0.932-4.3 4.38 14.5 14.6-14.5 14.5 4.3 4.4 14.6-14.6 4.4-4.3-4.4-4.4-14.6-14.6z"></path>
 </svg>
 </button>
 <button class="splide__arrow splide__arrow--next" 
 type="button"
 aria-label="{{ module.accessibility.next_button_label|default('Next image') }}"
 style="color: {{ module.styling.arrow_color }};">
 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" width="40" height="40" focusable="false">
 <path d="m15.5 0.932-4.3 4.38 14.5 14.6-14.5 14.5 4.3 4.4 14.6-14.6 4.4-4.3-4.4-4.4-14.6-14.6z"></path>
 </svg>
 </button>
 </div>
 {% endif %}

 <!-- Pagination Dots -->
 {% if module.slider_options.show_dots %}
 <div class="splide__pagination" 
 role="tablist"
 aria-label="Slider navigation">
 </div>
 {% endif %}
 </div>
 </div>
 {% endif %}
</section>
{% endif %} 

added a module.css

Spoiler

/* Image Slider Module Styles */
.image-slider-module {
 max-width: 1200px;
 margin: 0 auto;
 padding: 2rem 1rem;
 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
}

/* Header Styles */
.image-slider-header {
 text-align: center;
 margin-bottom: 2rem;
}

.image-slider-headline {
 font-size: 2.5rem;
 font-weight: 700;
 margin: 0 0 1rem 0;
 line-height: 1.2;
}

.image-slider-subline {
 font-size: 1.125rem;
 font-weight: 400;
 margin: 0;
 line-height: 1.6;
 opacity: 0.8;
}

/* Button Styles */
.image-slider-button-container {
 text-align: center;
 margin-bottom: 2rem;
}

.image-slider-button {
 display: inline-flex;
 align-items: center;
 justify-content: center;
 padding: 0.75rem 1.5rem;
 font-size: 1rem;
 font-weight: 600;
 text-decoration: none;
 border: 2px solid transparent;
 border-radius: 8px;
 cursor: pointer;
 transition: all 0.3s ease;
 min-height: 44px;
 min-width: 44px;
}

.image-slider-button:hover,
.image-slider-button:focus {
 transform: translateY(-2px);
 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
 outline: none;
}

.image-slider-button:active {
 transform: translateY(0);
}

/* Button Style Variants */
.image-slider-button--primary {
 background-color: #007cba;
 color: #ffffff;
}

.image-slider-button--primary:hover {
 background-color: #005a87;
}

.image-slider-button--secondary {
 background-color: #6c757d;
 color: #ffffff;
}

.image-slider-button--secondary:hover {
 background-color: #545b62;
}

.image-slider-button--outline {
 background-color: transparent;
 border-color: currentColor;
}

.image-slider-button--outline:hover {
 background-color: currentColor;
 color: #ffffff;
}

.image-slider-button--ghost {
 background-color: transparent;
 color: inherit;
}

.image-slider-button--ghost:hover {
 background-color: rgba(0, 0, 0, 0.1);
}

/* Slider Container */
.image-slider-container {
 position: relative;
 margin: 0 auto;
 max-width: 100%;
}

/* Splide Customization */
.image-slider {
 --splide-arrow-size: 2.5rem;
 --splide-arrow-bg: rgba(255, 255, 255, 0.9);
 --splide-arrow-color: #333333;
 --splide-pagination-color: #007cba;
}

.splide__arrow {
 background: var(--splide-arrow-bg);
 border: none;
 border-radius: 50%;
 box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
 transition: all 0.3s ease;
 width: var(--splide-arrow-size);
 height: var(--splide-arrow-size);
 display: flex;
 align-items: center;
 justify-content: center;
}

.splide__arrow:hover {
 background: rgba(255, 255, 255, 1);
 box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
 transform: scale(1.1);
}

.splide__arrow:focus {
 outline: 2px solid #007cba;
 outline-offset: 2px;
}

.splide__arrow svg {
 width: 1.5rem;
 height: 1.5rem;
 fill: currentColor;
}

.splide__arrow--prev svg {
 transform: rotate(180deg);
}

/* Pagination Dots */
.splide__pagination {
 bottom: -2rem;
 gap: 0.5rem;
}

.splide__pagination__page {
 background: rgba(0, 0, 0, 0.2);
 border: none;
 border-radius: 50%;
 width: 0.75rem;
 height: 0.75rem;
 transition: all 0.3s ease;
 cursor: pointer;
}

.splide__pagination__page:hover {
 background: rgba(0, 0, 0, 0.4);
 transform: scale(1.2);
}

.splide__pagination__page.is-active {
 background: var(--splide-pagination-color);
 transform: scale(1.3);
}

/* Image Styles */
.image-slider-figure {
 margin: 0;
 position: relative;
 overflow: hidden;
 border-radius: inherit;
}

.image-slider-image {
 width: 100%;
 height: auto;
 display: block;
 object-fit: cover;
 transition: transform 0.3s ease;
}

.image-slider-figure:hover .image-slider-image {
 transform: scale(1.05);
}

/* Image Caption */
.image-slider-caption {
 position: absolute;
 bottom: 0;
 left: 0;
 right: 0;
 background: linear-gradient(transparent, rgba(0, 0, 0, 0.8));
 color: #ffffff;
 padding: 2rem 1rem 1rem;
 margin: 0;
}

.image-slider-caption-title {
 font-size: 1.25rem;
 font-weight: 600;
 margin: 0 0 0.5rem 0;
 line-height: 1.3;
}

.image-slider-caption-description {
 font-size: 0.875rem;
 margin: 0;
 line-height: 1.4;
 opacity: 0.9;
}

/* Accessibility Improvements */
.splide__arrow:focus-visible,
.splide__pagination__page:focus-visible {
 outline: 2px solid #007cba;
 outline-offset: 2px;
}

/* Screen Reader Only Text */
.sr-only {
 position: absolute;
 width: 1px;
 height: 1px;
 padding: 0;
 margin: -1px;
 overflow: hidden;
 clip: rect(0, 0, 0, 0);
 white-space: nowrap;
 border: 0;
}

/* Responsive Design */
@media (max-width: 768px) {
 .image-slider-module {
 padding: 1rem 0.5rem;
 }

 .image-slider-headline {
 font-size: 2rem;
 }

 .image-slider-subline {
 font-size: 1rem;
 }

 .image-slider {
 --splide-arrow-size: 2rem;
 }

 .splide__arrow svg {
 width: 1.25rem;
 height: 1.25rem;
 }

 .image-slider-caption {
 padding: 1.5rem 0.75rem 0.75rem;
 }

 .image-slider-caption-title {
 font-size: 1.125rem;
 }

 .image-slider-caption-description {
 font-size: 0.8rem;
 }
}

@media (max-width: 480px) {
 .image-slider-headline {
 font-size: 1.75rem;
 }

 .image-slider {
 --splide-arrow-size: 1.75rem;
 }

 .splide__arrow svg {
 width: 1rem;
 height: 1rem;
 }

 .image-slider-caption {
 padding: 1rem 0.5rem 0.5rem;
 }
}

/* High Contrast Mode Support */
@media (prefers-contrast: high) {
 .splide__arrow {
 border: 2px solid currentColor;
 }

 .splide__pagination__page {
 border: 1px solid currentColor;
 }
}

/* Reduced Motion Support */
@media (prefers-reduced-motion: reduce) {
 .image-slider-image,
 .splide__arrow,
 .splide__pagination__page,
 .image-slider-button {
 transition: none;
 }

 .image-slider-figure:hover .image-slider-image {
 transform: none;
 }

 .splide__arrow:hover {
 transform: none;
 }

 .splide__pagination__page:hover,
 .splide__pagination__page.is-active {
 transform: none;
 }

 .image-slider-button:hover {
 transform: none;
 }
}

then a module.js

Spoiler

// Image Slider Module JavaScript
(function() {
 'use strict';

 // Wait for DOM to be ready
 document.addEventListener('DOMContentLoaded', function() {
 initializeImageSliders();
 });

 // Initialize all image sliders on the page
 function initializeImageSliders() {
 const sliders = document.querySelectorAll('.image-slider');

 sliders.forEach(function(slider) {
 if (slider && !slider.classList.contains('splide--initialized')) {
 initializeSlider(slider);
 }
 });
 }

 // Initialize individual slider
 function initializeSlider(slider) {
 // Get module data from data attributes or default values
 const moduleData = getModuleData(slider);

 // Create Splide instance
 const splide = new Splide(slider, {
 type: 'slide',
 perPage: parseInt(moduleData.slidesPerView) || 1,
 perMove: 1,
 gap: '1rem',
 padding: '1rem',
 autoplay: moduleData.autoplay || false,
 interval: (moduleData.autoplaySpeed || 3) * 1000,
 pauseOnHover: true,
 pauseOnFocus: true,
 resetProgress: false,
 arrows: moduleData.showArrows !== false,
 pagination: moduleData.showDots !== false,
 drag: true,
 snap: true,
 rewind: false,
 rewindByDrag: false,
 speed: 400,
 easing: 'cubic-bezier(0.25, 1, 0.5, 1)',
 focus: 'center',
 updateOnMove: true,
 trimSpace: true,
 omitEnd: false,
 keyboard: 'focused',
 direction: 'ltr',
 height: 'auto',
 width: '100%',
 fixedWidth: false,
 fixedHeight: false,
 cover: false,
 heightRatio: 0.5,
 focus: 0,
 isNavigation: false,
 updateOnMove: true,
 trimSpace: true,
 omitEnd: false,
 keyboard: 'focused',
 direction: 'ltr',
 height: 'auto',
 width: '100%',
 fixedWidth: false,
 fixedHeight: false,
 cover: false,
 heightRatio: 0.5,
 focus: 0,
 isNavigation: false,
 classes: {
 arrows: 'splide__arrows',
 arrow: 'splide__arrow',
 prev: 'splide__arrow--prev',
 next: 'splide__arrow--next',
 pagination: 'splide__pagination',
 page: 'splide__pagination__page',
 active: 'is-active',
 visible: 'is-visible',
 clone: 'is-clone',
 slide: 'splide__slide',
 track: 'splide__track',
 list: 'splide__list',
 container: 'splide__container'
 }
 });

 // Mount the slider
 splide.mount();

 // Add custom event listeners for accessibility
 addAccessibilityFeatures(splide, moduleData);

 // Mark as initialized
 slider.classList.add('splide--initialized');

 // Store reference for potential cleanup
 slider.splideInstance = splide;
 }

 // Get module data from data attributes or parent elements
 function getModuleData(slider) {
 const moduleContainer = slider.closest('.image-slider-module');
 if (!moduleContainer) return {};

 // Try to get data from data attributes (if set by HubSpot)
 const data = {
 autoplay: getDataAttribute(moduleContainer, 'autoplay') === 'true',
 autoplaySpeed: parseInt(getDataAttribute(moduleContainer, 'autoplay-speed')) || 3,
 showArrows: getDataAttribute(moduleContainer, 'show-arrows') !== 'false',
 showDots: getDataAttribute(moduleContainer, 'show-dots') !== 'false',
 slidesPerView: getDataAttribute(moduleContainer, 'slides-per-view') || '1'
 };

 return data;
 }

 // Helper function to get data attributes
 function getDataAttribute(element, attribute) {
 return element.getAttribute('data-' + attribute);
 }

 // Add accessibility features
 function addAccessibilityFeatures(splide, moduleData) {
 const track = splide.Components.Track;
 const pagination = splide.Components.Pagination;
 const arrows = splide.Components.Arrows;

 // Add keyboard navigation improvements
 if (arrows) {
 const prevButton = arrows.arrows.prev;
 const nextButton = arrows.arrows.next;

 if (prevButton) {
 prevButton.addEventListener('keydown', function(e) {
 if (e.key === 'Enter' || e.key === ' ') {
 e.preventDefault();
 splide.go('-');
 }
 });
 }

 if (nextButton) {
 nextButton.addEventListener('keydown', function(e) {
 if (e.key === 'Enter' || e.key === ' ') {
 e.preventDefault();
 splide.go('+');
 }
 });
 }
 }

 // Add ARIA live region for screen readers
 addLiveRegion(splide);

 // Add focus management
 addFocusManagement(splide);

 // Add swipe gesture announcements for screen readers
 addSwipeAnnouncements(splide);
 }

 // Add live region for screen reader announcements
 function addLiveRegion(splide) {
 const liveRegion = document.createElement('div');
 liveRegion.setAttribute('aria-live', 'polite');
 liveRegion.setAttribute('aria-atomic', 'true');
 liveRegion.className = 'sr-only';
 liveRegion.id = 'slider-live-region-' + Math.random().toString(36).substr(2, 9);

 splide.root.appendChild(liveRegion);

 // Announce slide changes
 splide.on('moved', function(newIndex) {
 const slides = splide.Components.Elements.slides;
 const currentSlide = slides[newIndex];
 const image = currentSlide.querySelector('img');
 const altText = image ? image.alt : 'Slide ' + (newIndex + 1);

 liveRegion.textContent = 'Now viewing: ' + altText;
 });
 }

 // Add focus management
 function addFocusManagement(splide) {
 const track = splide.Components.Track;

 // Keep focus within slider when navigating
 splide.on('moved', function() {
 const activeSlide = track.slides[splide.index];
 if (activeSlide) {
 const focusableElements = activeSlide.querySelectorAll('button, a, input, textarea, select, [tabindex]:not([tabindex="-1"])');
 if (focusableElements.length > 0) {
 focusableElements[0].focus();
 }
 }
 });
 }

 // Add swipe gesture announcements
 function addSwipeAnnouncements(splide) {
 let startX = 0;
 let startY = 0;
 let isSwiping = false;

 splide.root.addEventListener('touchstart', function(e) {
 startX = e.touches[0].clientX;
 startY = e.touches[0].clientY;
 isSwiping = false;
 }, { passive: true });

 splide.root.addEventListener('touchmove', function(e) {
 if (!isSwiping) {
 const deltaX = Math.abs(e.touches[0].clientX - startX);
 const deltaY = Math.abs(e.touches[0].clientY - startY);

 if (deltaX > deltaY && deltaX > 10) {
 isSwiping = true;
 }
 }
 }, { passive: true });

 splide.root.addEventListener('touchend', function(e) {
 if (isSwiping) {
 const deltaX = e.changedTouches[0].clientX - startX;
 const threshold = 50;

 if (Math.abs(deltaX) > threshold) {
 const liveRegion = document.getElementById('slider-live-region-' + splide.root.querySelector('[aria-live]').id.split('-').pop());
 if (liveRegion) {
 liveRegion.textContent = deltaX > 0 ? 'Swiped right, previous slide' : 'Swiped left, next slide';
 }
 }
 }
 }, { passive: true });
 }

 // Handle window resize
 window.addEventListener('resize', function() {
 const sliders = document.querySelectorAll('.splide--initialized');
 sliders.forEach(function(slider) {
 if (slider.splideInstance) {
 slider.splideInstance.refresh();
 }
 });
 });

 // Handle visibility change (pause autoplay when tab is not visible)
 document.addEventListener('visibilitychange', function() {
 const sliders = document.querySelectorAll('.splide--initialized');
 sliders.forEach(function(slider) {
 if (slider.splideInstance) {
 if (document.hidden) {
 slider.splideInstance.Components.Autoplay.pause();
 } else {
 slider.splideInstance.Components.Autoplay.play();
 }
 }
 });
 });

 // Cleanup function for module removal
 window.cleanupImageSliders = function() {
 const sliders = document.querySelectorAll('.splide--initialized');
 sliders.forEach(function(slider) {
 if (slider.splideInstance) {
 slider.splideInstance.destroy();
 slider.classList.remove('splide--initialized');
 delete slider.splideInstance;
 }
 });
 };

})();

created a README.md (which is a nice to have but was never requested)

Spoiler

# HubSpot Image Slider Module

A comprehensive, accessible image slider module for HubSpot CMS with extensive customization options and Splide.js integration.

## Features

### 🎨 Content Management
- **Headline & Subline**: Customizable text content with color options
- **Optional Button**: Configurable button above the slider with multiple style variants
- **Image Repeater**: Add unlimited images with titles and descriptions
- **Accessibility**: Built-in screen reader support and semantic HTML

### 🎯 Slider Functionality
- **Splide.js Integration**: Smooth, performant sliding with touch support
- **Autoplay**: Configurable automatic sliding with pause on hover/focus
- **Navigation**: Optional arrows and dot indicators
- **Responsive**: Multiple slides per view with responsive breakpoints
- **Keyboard Navigation**: Full keyboard accessibility

### 🎨 Styling Options
- **Color Customization**: Headline, subline, button, arrow, and dot colors
- **Image Styling**: Configurable border radius (none to circular)
- **Button Styles**: Primary, secondary, outline, and ghost variants
- **Responsive Design**: Mobile-optimized with breakpoint-specific styling

### ♿ Accessibility Features
- **Screen Reader Support**: ARIA labels, live regions, and semantic markup
- **Keyboard Navigation**: Full keyboard support with focus management
- **High Contrast Mode**: Enhanced visibility for accessibility
- **Reduced Motion**: Respects user motion preferences
- **Touch Gestures**: Swipe support with screen reader announcements

## File Structure

```
image-slider-module/
├── module.json # Module configuration
├── fields.json # Field definitions and options
├── module.html # HubL template with semantic HTML
├── module.css # Comprehensive styling with accessibility
├── module.js # Splide.js initialization and features
└── README.md # This documentation
```

## Installation

1. **Create Module Directory**: Create a new directory in your HubSpot design manager
2. **Upload Files**: Upload all module files to the directory
3. **Configure Module**: Set up the module in HubSpot's design manager
4. **Add to Templates**: Include the module in your page templates

## Field Configuration

### Content Fields
- `headline`: Main title text
- `subline`: Subtitle or description
- `show_button`: Toggle button visibility
- `button_text`: Button label text
- `button_link`: Button destination URL
- `images`: Repeater field for slider images

### Slider Options
- `autoplay`: Enable automatic sliding
- `autoplay_speed`: Time between slides (1-10 seconds)
- `show_arrows`: Display navigation arrows
- `show_dots`: Display pagination dots
- `slides_per_view`: Number of visible slides (1-4)

### Styling Options
- `headline_color`: Headline text color
- `subline_color`: Subline text color
- `image_border_radius`: Image corner rounding
- `button_style`: Button appearance variant
- `button_color`: Button background color
- `button_text_color`: Button text color
- `arrow_color`: Navigation arrow color
- `dot_color`: Pagination dot color

### Accessibility
- `slider_label`: Screen reader label for slider
- `previous_button_label`: Previous button accessibility text
- `next_button_label`: Next button accessibility text

## Usage Examples

### Basic Image Slider
```hubl
{% module "image_slider" path="/path/to/module" %}
```

### With Custom Content
```hubl
{% module "image_slider" 
 path="/path/to/module",
 headline="Our Portfolio",
 subline="Explore our latest work",
 show_button=true,
 button_text="View All Projects",
 button_link={
 "url": "/portfolio",
 "open_in_new_tab": false
 }
%}
```

## Accessibility Compliance

### WCAG 2.1 AA Standards
- ✅ **Perceivable**: Proper alt text, color contrast, screen reader support
- ✅ **Operable**: Keyboard navigation, focus management, touch gestures
- ✅ **Understandable**: Clear labels, consistent navigation
- ✅ **Robust**: Semantic HTML, ARIA attributes, progressive enhancement

### Screen Reader Features
- ARIA live regions for slide announcements
- Proper button and link labeling
- Focus management within slider
- Touch gesture announcements

### Keyboard Navigation
- Tab navigation through all interactive elements
- Enter/Space activation for buttons
- Arrow key navigation (when focused)
- Escape key to exit slider mode

## Browser Support

- **Modern Browsers**: Chrome 60+, Firefox 55+, Safari 12+, Edge 79+
- **Mobile**: iOS Safari 12+, Chrome Mobile 60+
- **Accessibility**: NVDA, JAWS, VoiceOver, TalkBack

## Performance Features

- **Lazy Loading**: Images load only when needed
- **Optimized Animations**: Hardware-accelerated CSS transitions
- **Memory Management**: Proper cleanup on module removal
- **Responsive Images**: Automatic sizing and optimization

## Customization

### CSS Customization
The module uses CSS custom properties for easy theming:

```css
.image-slider {
 --splide-arrow-size: 2.5rem;
 --splide-arrow-bg: rgba(255, 255, 255, 0.9);
 --splide-arrow-color: #333333;
 --splide-pagination-color: #007cba;
}
```

### JavaScript Extensions
The module provides hooks for custom functionality:

```javascript
// Listen for slide changes
splide.on('moved', function(newIndex) {
 console.log('Slide changed to:', newIndex);
});

// Custom initialization
document.addEventListener('DOMContentLoaded', function() {
 // Your custom code here
});
```

## Troubleshooting

### Common Issues

1. **Slider Not Initializing**
 - Check if Splide.js is loaded
 - Verify module structure is correct
 - Check browser console for errors

2. **Images Not Displaying**
 - Verify image URLs are accessible
 - Check HubSpot file manager permissions
 - Ensure proper image format (JPG, PNG, WebP)

3. **Accessibility Issues**
 - Test with screen readers
 - Verify keyboard navigation
 - Check color contrast ratios

### Debug Mode
Enable debug logging by adding this to your page:

```javascript
window.splideDebug = true;
```

## Contributing

1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Test thoroughly
5. Submit a pull request

## License

This module is provided as-is for use with HubSpot CMS. Please ensure compliance with HubSpot's terms of service and your organization's policies.

## Support

For issues and questions:
1. Check the troubleshooting section
2. Review HubSpot's module documentation
3. Test in different browsers and devices
4. Verify accessibility with screen readers

---

**Version**: 1.0.0 
**Last Updated**: 2024 
**HubSpot Compatibility**: CMS Hub Professional/Enterprise

created a sample-data.json (by itself; not requested)

Spoiler

{
 "headline": "Our Amazing Portfolio",
 "subline": "Discover our latest projects and creative work",
 "show_button": true,
 "button_text": "View All Projects",
 "button_link": {
 "url": "/portfolio",
 "open_in_new_tab": false,
 "no_follow": false
 },
 "images": [
 {
 "image": {
 "src": "https://via.placeholder.com/800x600/007cba/ffffff?text=Project+1",
 "alt": "Modern web application interface",
 "width": 800,
 "height": 600
 },
 "image_title": "E-commerce Platform",
 "image_description": "A modern, responsive e-commerce solution with advanced features"
 },
 {
 "image": {
 "src": "https://via.placeholder.com/800x600/28a745/ffffff?text=Project+2",
 "alt": "Mobile app design mockup",
 "width": 800,
 "height": 600
 },
 "image_title": "Mobile Banking App",
 "image_description": "Secure and intuitive mobile banking experience for iOS and Android"
 },
 {
 "image": {
 "src": "https://via.placeholder.com/800x600/dc3545/ffffff?text=Project+3",
 "alt": "Dashboard analytics interface",
 "width": 800,
 "height": 600
 },
 "image_title": "Analytics Dashboard",
 "image_description": "Real-time data visualization and reporting platform"
 },
 {
 "image": {
 "src": "https://via.placeholder.com/800x600/ffc107/000000?text=Project+4",
 "alt": "Creative branding design",
 "width": 800,
 "height": 600
 },
 "image_title": "Brand Identity Design",
 "image_description": "Complete brand identity package including logo, colors, and guidelines"
 },
 {
 "image": {
 "src": "https://via.placeholder.com/800x600/6f42c1/ffffff?text=Project+5",
 "alt": "3D product visualization",
 "width": 800,
 "height": 600
 },
 "image_title": "3D Product Visualization",
 "image_description": "High-quality 3D renders for product marketing and e-commerce"
 }
 ],
 "slider_options": {
 "autoplay": true,
 "autoplay_speed": 4,
 "show_arrows": true,
 "show_dots": true,
 "slides_per_view": "1"
 },
 "styling": {
 "headline_color": "#2c3e50",
 "subline_color": "#7f8c8d",
 "image_border_radius": "12px",
 "button_style": "primary",
 "button_color": "#007cba",
 "button_text_color": "#ffffff",
 "arrow_color": "#2c3e50",
 "dot_color": "#007cba"
 },
 "accessibility": {
 "slider_label": "Portfolio image slider",
 "previous_button_label": "Previous project",
 "next_button_label": "Next project"
 }
}

as well as a demo.html (outside the request; completely wrong useage)

Spoiler

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>HubSpot Image Slider Module Demo</title>

 <!-- Splide.js CSS -->
 <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@splidejs/splide@4.1.4/dist/css/splide.min.css">

 <!-- Module CSS -->
 <link rel="stylesheet" href="module.css">

 <style>
 body {
 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
 margin: 0;
 padding: 20px;
 background-color: #f8f9fa;
 }

 .demo-container {
 max-width: 1200px;
 margin: 0 auto;
 background: white;
 border-radius: 12px;
 box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
 overflow: hidden;
 }

 .demo-header {
 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
 color: white;
 padding: 2rem;
 text-align: center;
 }

 .demo-header h1 {
 margin: 0 0 0.5rem 0;
 font-size: 2.5rem;
 font-weight: 700;
 }

 .demo-header p {
 margin: 0;
 font-size: 1.125rem;
 opacity: 0.9;
 }

 .demo-content {
 padding: 2rem;
 }

 .feature-grid {
 display: grid;
 grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
 gap: 1.5rem;
 margin-top: 2rem;
 }

 .feature-card {
 background: #f8f9fa;
 padding: 1.5rem;
 border-radius: 8px;
 border-left: 4px solid #007cba;
 }

 .feature-card h3 {
 margin: 0 0 0.5rem 0;
 color: #2c3e50;
 }

 .feature-card p {
 margin: 0;
 color: #6c757d;
 line-height: 1.5;
 }
 </style>
</head>
<body>
 <div class="demo-container">
 <div class="demo-header">
 <h1>HubSpot Image Slider Module</h1>
 <p>A comprehensive, accessible image slider with Splide.js integration</p>
 </div>

 <div class="demo-content">
 <!-- Module Demo -->
 <section class="image-slider-module" aria-label="Portfolio image slider">

 <!-- Header Section -->
 <header class="image-slider-header">
 <h2 class="image-slider-headline" style="color: #2c3e50;">
 Our Amazing Portfolio
 </h2>

 <p class="image-slider-subline" style="color: #7f8c8d;">
 Discover our latest projects and creative work
 </p>
 </header>

 <!-- Optional Button -->
 <div class="image-slider-button-container">
 <a href="/portfolio" 
 class="image-slider-button image-slider-button--primary"
 style="background-color: #007cba; color: #ffffff;">
 View All Projects
 </a>
 </div>

 <!-- Image Slider -->
 <div class="image-slider-container">
 <div class="splide image-slider" 
 role="region" 
 aria-label="Portfolio image slider">

 <div class="splide__track">
 <ul class="splide__list">
 <li class="splide__slide">
 <figure class="image-slider-figure">
 <img src="https://via.placeholder.com/800x600/007cba/ffffff?text=Project+1" 
 alt="Modern web application interface"
 class="image-slider-image"
 style="border-radius: 12px;"
 width="800"
 height="600"
 loading="lazy">

 <figcaption class="image-slider-caption">
 <h3 class="image-slider-caption-title">E-commerce Platform</h3>
 <p class="image-slider-caption-description">A modern, responsive e-commerce solution with advanced features</p>
 </figcaption>
 </figure>
 </li>

 <li class="splide__slide">
 <figure class="image-slider-figure">
 <img src="https://via.placeholder.com/800x600/28a745/ffffff?text=Project+2" 
 alt="Mobile app design mockup"
 class="image-slider-image"
 style="border-radius: 12px;"
 width="800"
 height="600"
 loading="lazy">

 <figcaption class="image-slider-caption">
 <h3 class="image-slider-caption-title">Mobile Banking App</h3>
 <p class="image-slider-caption-description">Secure and intuitive mobile banking experience for iOS and Android</p>
 </figcaption>
 </figure>
 </li>

 <li class="splide__slide">
 <figure class="image-slider-figure">
 <img src="https://via.placeholder.com/800x600/dc3545/ffffff?text=Project+3" 
 alt="Dashboard analytics interface"
 class="image-slider-image"
 style="border-radius: 12px;"
 width="800"
 height="600"
 loading="lazy">

 <figcaption class="image-slider-caption">
 <h3 class="image-slider-caption-title">Analytics Dashboard</h3>
 <p class="image-slider-caption-description">Real-time data visualization and reporting platform</p>
 </figcaption>
 </figure>
 </li>

 <li class="splide__slide">
 <figure class="image-slider-figure">
 <img src="https://via.placeholder.com/800x600/ffc107/000000?text=Project+4" 
 alt="Creative branding design"
 class="image-slider-image"
 style="border-radius: 12px;"
 width="800"
 height="600"
 loading="lazy">

 <figcaption class="image-slider-caption">
 <h3 class="image-slider-caption-title">Brand Identity Design</h3>
 <p class="image-slider-caption-description">Complete brand identity package including logo, colors, and guidelines</p>
 </figcaption>
 </figure>
 </li>

 <li class="splide__slide">
 <figure class="image-slider-figure">
 <img src="https://via.placeholder.com/800x600/6f42c1/ffffff?text=Project+5" 
 alt="3D product visualization"
 class="image-slider-image"
 style="border-radius: 12px;"
 width="800"
 height="600"
 loading="lazy">

 <figcaption class="image-slider-caption">
 <h3 class="image-slider-caption-title">3D Product Visualization</h3>
 <p class="image-slider-caption-description">High-quality 3D renders for product marketing and e-commerce</p>
 </figcaption>
 </figure>
 </li>
 </ul>
 </div>

 <!-- Navigation Arrows -->
 <div class="splide__arrows">
 <button class="splide__arrow splide__arrow--prev" 
 type="button"
 aria-label="Previous project"
 style="color: #2c3e50;">
 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" width="40" height="40" focusable="false">
 <path d="m15.5 0.932-4.3 4.38 14.5 14.6-14.5 14.5 4.3 4.4 14.6-14.6 4.4-4.3-4.4-4.4-14.6-14.6z"></path>
 </svg>
 </button>
 <button class="splide__arrow splide__arrow--next" 
 type="button"
 aria-label="Next project"
 style="color: #2c3e50;">
 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" width="40" height="40" focusable="false">
 <path d="m15.5 0.932-4.3 4.38 14.5 14.6-14.5 14.5 4.3 4.4 14.6-14.6 4.4-4.3-4.4-4.4-14.6-14.6z"></path>
 </svg>
 </button>
 </div>

 <!-- Pagination Dots -->
 <div class="splide__pagination" 
 role="tablist"
 aria-label="Slider navigation">
 </div>
 </div>
 </div>
 </section>

 <!-- Features Overview -->
 <div class="feature-grid">
 <div class="feature-card">
 <h3>🎨 Content Management</h3>
 <p>Headline, subline, optional button, and unlimited images with titles and descriptions for accessibility.</p>
 </div>

 <div class="feature-card">
 <h3>🎯 Slider Functionality</h3>
 <p>Smooth Splide.js integration with autoplay, navigation arrows, dot indicators, and responsive design.</p>
 </div>

 <div class="feature-card">
 <h3>🎨 Styling Options</h3>
 <p>Extensive customization including colors, border radius, button styles, and responsive breakpoints.</p>
 </div>

 <div class="feature-card">
 <h3>♿ Accessibility</h3>
 <p>WCAG 2.1 AA compliant with screen reader support, keyboard navigation, and semantic HTML.</p>
 </div>
 </div>
 </div>
 </div>

 <!-- Splide.js JavaScript -->
 <script src="https://cdn.jsdelivr.net/npm/@splidejs/splide@4.1.4/dist/js/splide.min.js"></script>

 <!-- Module JavaScript -->
 <script src="module.js"></script>

 <script>
 // Initialize the demo slider with custom options
 document.addEventListener('DOMContentLoaded', function() {
 const slider = document.querySelector('.image-slider');
 if (slider) {
 const splide = new Splide(slider, {
 type: 'slide',
 perPage: 1,
 perMove: 1,
 gap: '1rem',
 padding: '1rem',
 autoplay: true,
 interval: 4000,
 pauseOnHover: true,
 pauseOnFocus: true,
 arrows: true,
 pagination: true,
 drag: true,
 snap: true,
 speed: 400,
 easing: 'cubic-bezier(0.25, 1, 0.5, 1)',
 keyboard: 'focused'
 });

 splide.mount();
 }
 });
 </script>
</body>
</html>

I’ve uploaded the whole chat as attachment. You can open it with a text editor or something like VSCode

ChatGPT

As Cursor it started with the fields.json

Spoiler

[
 {
 "name": "headline",
 "label": "Headline",
 "type": "text",
 "required": true
 },
 {
 "name": "subline",
 "label": "Subline",
 "type": "text"
 },
 {
 "name": "show_button",
 "label": "Show Button",
 "type": "boolean"
 },
 {
 "name": "button_text",
 "label": "Button Text",
 "type": "text",
 "visibility": {
 "controlling_field": "show_button",
 "controlling_value_regex": "true"
 }
 },
 {
 "name": "button_url",
 "label": "Button URL",
 "type": "url",
 "visibility": {
 "controlling_field": "show_button",
 "controlling_value_regex": "true"
 }
 },
 {
 "name": "images",
 "label": "Image Slider Items",
 "type": "repeatable_group",
 "children": [
 {
 "name": "image",
 "label": "Image",
 "type": "image"
 },
 {
 "name": "alt_text",
 "label": "Alt Text",
 "type": "text"
 }
 ]
 },
 {
 "name": "headline_color",
 "label": "Headline Color",
 "type": "color",
 "default": "#000000"
 },
 {
 "name": "subline_color",
 "label": "Subline Color",
 "type": "color",
 "default": "#333333"
 },
 {
 "name": "image_border_radius",
 "label": "Image Border Radius (px)",
 "type": "number",
 "default": 8
 },
 {
 "name": "button_bg_color",
 "label": "Button Background Color",
 "type": "color",
 "default": "#0d75dd"
 },
 {
 "name": "button_text_color",
 "label": "Button Text Color",
 "type": "color",
 "default": "#ffffff"
 },
 {
 "name": "button_border_radius",
 "label": "Button Border Radius (px)",
 "type": "number",
 "default": 6
 }
]

but the fields.json won’t work properly as it’s missing quite a few important elements and contains errors. Also it did not include the repeater functionality correctly.

It proceeded with the module.html

Spoiler

<section class="custom-slider-module">
 <header>
 <h2 style="color: {{ module.headline_color }}">{{ module.headline }}</h2>
 {% if module.subline %}
 <p style="color: {{ module.subline_color }}">{{ module.subline }}</p>
 {% endif %}
 {% if module.show_button %}
 <a 
 href="{{ module.button_url }}" 
 class="custom-button" 
 style="background-color: {{ module.button_bg_color }}; color: {{ module.button_text_color }}; border-radius: {{ module.button_border_radius }}px;"
 aria-label="{{ module.button_text }}"
 >
 {{ module.button_text }}
 </a>
 {% endif %}
 </header>

 {% if module.images %}
 <div class="splide" aria-label="Image Slider">
 <div class="splide__track">
 <ul class="splide__list">
 {% for item in module.images %}
 <li class="splide__slide">
 <img 
 src="{{ item.image.src }}" 
 alt="{{ item.alt_text|default('Slide image') }}" 
 loading="lazy"
 style="border-radius: {{ module.image_border_radius }}px;" 
 />
 </li>
 {% endfor %}
 </ul>
 </div>
 </div>
 {% endif %}
</section>

the module.css

Spoiler

.custom-slider-module {
 text-align: center;
 padding: 2rem;
}

.custom-slider-module h2 {
 font-size: 2rem;
 margin-bottom: 0.5rem;
}

.custom-slider-module p {
 font-size: 1.1rem;
 margin-bottom: 1.5rem;
}

.custom-button {
 display: inline-block;
 padding: 0.75rem 1.5rem;
 font-weight: bold;
 text-decoration: none;
 transition: background-color 0.3s ease;
 margin-bottom: 2rem;
}

.custom-button:hover,
.custom-button:focus {
 filter: brightness(0.9);
}

the module.js

Spoiler

document.addEventListener('DOMContentLoaded', function () {
 if (typeof Splide !== 'undefined') {
 new Splide('.splide', {
 type : 'loop',
 perPage: 1,
 autoplay: true,
 pagination: true,
 arrows: true,
 }).mount();
 }
});

last but not least it told me to include splide.js into my template or theme.

Spoiler

<!-- Add to module.html head_end or via theme settings -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@splidejs/splide@4.1.4/dist/css/splide.min.css">
<script src="https://cdn.jsdelivr.net/npm/@splidejs/splide@4.1.4/dist/js/splide.min.js"></script>

^ this is possible but will load the JavaScript regardless of if it’s being used or not. Therefore there’s a risk of negative performance impact.

Link to the whole chat

Conclusion

Cursor is the winner as it gives you a way better starting point.

Why starting point?

Based on the super simple prompt and without rules that define things like “create a module in a folder like NAME.module” or “here’s the doc about creating custom HubSpot modules” it provided a way better and cleaner code.

Is the code perfect?

No, but a developer can optimize it much easier than the ChatGPT version

Would I use Cursor to help me create modules in the future?
Yes/No. Over the time I’ve developed my own development style and setup which my clients benefit from and is different than the result of the basic test prompt. For ideas how to write something or adding features I might not think of - yes. But the “final” code would most likely be quite different unless I’d create and add my cursorrules to the prompt.

Would I recommend Cursor for real-life useage?

Only if you’re aware that you’ll need to provide better prompts, instructions and double and triple check the code. Debugging and rewriting this may leave you with something that has almost nothing left in common with the initial module from the Ai.

Is the result any good?

I would say that it does not meet my personal quality standards but for something like learning and understanding how modules can be written, it’s a way better result than ChatGPT

1172517_1_cursor_creating_a_hubspot_module_with_i.txt (54.5 KB)

AHA! I don’t know much about code, but you make everything sound so easy! Thanks for sharing both options and the chats with each AI.

In my case, I will rely on your winner option, ‌😁‌ but this reminds me how crucial that foundational knowledge is - the stuff we learned back in the pre-AI days, hahaha!

Otherwise, how could you know when the AI is doing something wrong, right?

This is so cool - thanks again for sharing!

I have done this AI Head-to-Head a few times between ChatGPT and Gemini.
Most recently, I tested out the same image gen instructions in a custom GPT in ChatGPT against a Gem in Gemini. The results are so vastly different it is fascinating.

Example 1:


ChatGPT output

Gemini Output

Example 2:

**

ChatGPT output


Gemini Output**

Since I was generating images to go along with social media posts, I decided to keep both and to just use different images for different platforms.
But it is a good lesson to know that each AI tool will give you different outputs even with the exact same input. You just have to test them out to see what works for your and your business.

Danielle, this is fantastic! :raising_hands: I would say that there’s no definitive ‘winner’ here, as it entirely depends on the user’s intent, audience, platform, and more. Thank you for putting together this comparison; it’s clear they offer completely different strengths, which is truly fascinating.

Having both options is valuable, whether we need something conceptual or highly specific.

Appreciate you sharing this!:grin:

This is both interesting and a bit funny to see how different AI platforms work!:grin::grinning_face:

I used two platforms, ChatGPT Free and Grok, to generate a LinkedIn profile banner with the same prompt:

“Create a LinkedIn profile banner image for a HubSpot automation expert who holds a HubSpot Community Champion badge and is a Certified HubSpot Solutions Provider. The banner should reflect their professional identity and can be used on their LinkedIn profile.”

The results were completely different!


Chat GPT Generated


Grok Generated

This is a great reminder that AI tools produce varied outputs even when given the exact same input. It’s a good practice, whether for individuals or businesses, to always double-check the results and choose what truly fits your brand, rather than blindly copying and pasting.:smiling_face_with_sunglasses::winking_face_with_tongue:

At the end of the day, every AI has its own strengths and quirks; you just have to test and see what works best for you.

Wow, vastly different outcomes. I have not (and dont plan to) use Grok. But we have a paid ChatGPT Team plan, as well as paid Gemini, Perplexity, Canva and Copilot plans. So lots of options to choose from and compare.

I believe Perplexity and ChatGPT are great in terms of providing detailed solutions and linking to relevant articles that help users dive deeper and understand how the answer was generated.

Techniques like Persona Pattern, Question Refinement Pattern, or Chain of Thought Prompting play a key role in enhancing the quality of responses.

However, the most important factor with any AI platform is the prompt itself; the structure, clarity, and level of detail you provide significantly impact the quality of the output.

Just for fun and since you and @Anton have already explored Gemini and Cursor alongside ChatGPT, I figured, why not try another platform too? So I gave Grok a shot… and let’s just say, your decision “I have not (and don’t plan to) use Grok.” might’ve been spot-on in my case :joy:

TLDR: This doesn’t make sense to me because they’re meant to be a team, not competitors to one another.
This is where I run into a common problem with the way in which people approach AI. They look at them as “tools” and not people and because of that you’re pegging yourself into a corner that you shouldn’t be.
AI’s are essentially replicas of our brains, they attempt to think like we do in order to help us problem solve, troubleshoot, and work together. That last part is the most important because it lends itself to the fact that each model is not the same as one another.

If you’re appropriately looking at them as what they are, then you start to quickly understand that it’s essentially a group of people who think about problems differently. The same prompt across multiple models shouldn’t yeild the exact same answer, otherwise what’s the point?

Say you ask a model about the color of an apple, but just that, you don’t specify. One model may ask you which type of apple before beginning to answer. Another might give you all the potential colors for all the types of apples and then ask you which one you meant. Maybe another model not only gives you that data but then also goes further to tell you about why the colors are the way they are. Could be that there’s a model that just picks the most common apple colors and says, “Either red, green, or yellow.”

It’s like going to a doctor about a diagnosis, only here you have a multitude at your fingertips to ensure that everyone is giving the right answer even if they go about it differently!
So I use them in conjunction with each other because it creates a much richer response and a more dynamic method of problem solving!

You’ve hit on a key insight. The power isn’t in uniformity, but in leveraging the distinct “thinking styles” of different AI models for a more complete picture.

Thanks for sharing :grin:

I tested ChatGPT-4o vs Claude 3 Opus for text summarization. Gave both the same long article and compared them on: Accuracy Clarity Tone Speed ChatGPT gave a more balanced and structured summary, while Claude leaned a bit too verbose. Both were great, but ChatGPT-4o wins this round for being crisp and easy to follow.