{% comment %}
Section: Customer Reviews
File: sections/customer-reviews.liquid
{% endcomment %}
<div class="reviews-section">
{% if section.settings.section_title != blank %}
<div class="reviews-header">
{% if section.settings.eyebrow != blank %}
<p class="reviews-eyebrow">{{ section.settings.eyebrow }}</p>
{% endif %}
<h2 class="reviews-title">{{ section.settings.section_title }}</h2>
{% if section.settings.section_subtitle != blank %}
<p class="reviews-subtitle">{{ section.settings.section_subtitle }}</p>
{% endif %}
</div>
{% endif %}
<div class="reviews-carousel-wrapper">
<button class="reviews-nav reviews-nav--prev" aria-label="Previous">←</button>
<div class="reviews-track-outer">
<div class="reviews-track" id="reviews-track-{{ section.id }}">
{% for block in section.blocks %}
{% if block.type == 'review' %}
<div class="review-card" {{ block.shopify_attributes }}>
<div class="review-stars">
{% assign star_count = block.settings.rating %}
{% for i in (1..5) %}
{% if i <= star_count %}
<span class="star star--filled">★</span>
{% else %}
<span class="star star--empty">★</span>
{% endif %}
{% endfor %}
</div>
{% if block.settings.review_text != blank %}
<p class="review-text">"{{ block.settings.review_text }}"</p>
{% endif %}
<div class="review-author">
<div class="review-avatar">
{% if block.settings.avatar != blank %}
<img
src="{{ block.settings.avatar | image_url: width: 80 }}"
alt="{{ block.settings.author_name | escape }}"
loading="lazy"
>
{% else %}
<span class="review-avatar-initials">
{{ block.settings.author_name | upcase | slice: 0, 2 }}
</span>
{% endif %}
</div>
<div class="review-author-info">
{% if block.settings.author_name != blank %}
<strong class="review-author-name">{{ block.settings.author_name }}</strong>
{% endif %}
{% if block.settings.author_location != blank %}
<span class="review-author-location">{{ block.settings.author_location }}</span>
{% endif %}
</div>
</div>
</div>
{% endif %}
{% endfor %}
</div>
</div>
<button class="reviews-nav reviews-nav--next" aria-label="Next">→</button>
</div>
<div class="reviews-dots" id="reviews-dots-{{ section.id }}">
{% for block in section.blocks %}
{% if block.type == 'review' %}
<button
class="reviews-dot {% if forloop.first %}reviews-dot--active{% endif %}"
data-index="{{ forloop.index0 }}"
aria-label="Go to review {{ forloop.index }}"
></button>
{% endif %}
{% endfor %}
</div>
</div>
<style>
.reviews-section {
background-color: {{ section.settings.background_color }};
padding: {{ section.settings.padding_top }}px 32px {{ section.settings.padding_bottom }}px;
}
.reviews-header {
text-align: center;
margin-bottom: 40px;
}
.reviews-eyebrow {
font-size: 11px;
letter-spacing: .14em;
text-transform: uppercase;
color: {{ section.settings.eyebrow_color }};
margin: 0 0 10px;
}
.reviews-title {
font-size: {{ section.settings.title_size }}px;
color: {{ section.settings.title_color }};
line-height: 1.25;
margin: 0 0 10px;
}
.reviews-subtitle {
font-size: 14px;
color: {{ section.settings.eyebrow_color }};
margin: 0;
opacity: 0.8;
}
.reviews-carousel-wrapper {
max-width: {{ section.settings.max_width }}px;
margin: 0 auto;
display: flex;
align-items: center;
gap: 12px;
}
.reviews-track-outer {
flex: 1;
overflow: hidden;
}
.reviews-track {
display: flex;
gap: {{ section.settings.card_gap }}px;
transition: transform 0.5s ease;
will-change: transform;
}
.review-card {
background-color: {{ section.settings.card_bg }};
border: 1px solid {{ section.settings.card_border_color }};
padding: {{ section.settings.card_padding }}px;
display: flex;
flex-direction: column;
gap: 16px;
flex-shrink: 0;
box-sizing: border-box;
}
.review-stars {
display: flex;
gap: 2px;
}
.star {
font-size: {{ section.settings.star_size }}px;
line-height: 1;
}
.star--filled { color: {{ section.settings.star_color }}; }
.star--empty { color: {{ section.settings.card_border_color }}; }
.review-text {
font-size: {{ section.settings.review_text_size }}px;
color: {{ section.settings.review_text_color }};
line-height: 1.75;
margin: 0;
font-style: italic;
flex: 1;
}
.review-author {
display: flex;
align-items: center;
gap: 12px;
padding-top: 8px;
border-top: 1px solid {{ section.settings.card_border_color }};
}
.review-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
overflow: hidden;
background-color: {{ section.settings.avatar_bg }};
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.review-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.review-avatar-initials {
font-size: 13px;
font-weight: 500;
color: {{ section.settings.avatar_text_color }};
letter-spacing: .04em;
}
.review-author-info {
display: flex;
flex-direction: column;
gap: 2px;
}
.review-author-name {
font-size: 13px;
font-weight: 600;
color: {{ section.settings.author_name_color }};
display: block;
}
.review-author-location {
font-size: 12px;
color: {{ section.settings.author_location_color }};
opacity: 0.75;
}
.reviews-nav {
background: {{ section.settings.card_bg }};
border: 1px solid {{ section.settings.card_border_color }};
color: {{ section.settings.title_color }};
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 18px;
flex-shrink: 0;
transition: background .2s;
}
.reviews-nav:hover {
background: {{ section.settings.card_border_color }};
}
.reviews-dots {
display: flex;
justify-content: center;
gap: 8px;
margin-top: 24px;
}
.reviews-dot {
width: 8px;
height: 8px;
border-radius: 50%;
border: none;
background: {{ section.settings.card_border_color }};
cursor: pointer;
padding: 0;
transition: all .3s;
}
.reviews-dot--active {
background: {{ section.settings.star_color }};
width: 24px;
border-radius: 4px;
}
@media (max-width: 749px) {
.reviews-section {
padding-left: 16px;
padding-right: 16px;
}
.reviews-nav { display: none; }
}
</style>
<script>
(function () {
var SECTION_ID = '{{ section.id }}';
var VISIBLE = {{ section.settings.cards_visible }};
var GAP = {{ section.settings.card_gap }};
var AUTO_DELAY = {{ section.settings.autoplay_speed }} * 1000;
var track = document.getElementById('reviews-track-' + SECTION_ID);
var dotsWrap = document.getElementById('reviews-dots-' + SECTION_ID);
var section = track.closest('.reviews-section');
var cards = Array.from(track.querySelectorAll('.review-card'));
var dots = Array.from(dotsWrap.querySelectorAll('.reviews-dot'));
var total = cards.length;
var current = 0;
var timer = null;
function visibleCount() {
return window.innerWidth <= 749 ? 1 : VISIBLE;
}
function setCardWidths() {
var vis = visibleCount();
var outer = track.parentElement.offsetWidth;
var w = (outer - GAP * (vis - 1)) / vis;
cards.forEach(function (c) {
c.style.width = w + 'px';
});
}
function maxIndex() {
return Math.max(0, total - visibleCount());
}
function goTo(index) {
current = ((index % total) + total) % total;
if (current > maxIndex()) current = 0;
var cardWidth = cards[0].offsetWidth;
var offset = current * (cardWidth + GAP);
track.style.transform = 'translateX(-' + offset + 'px)';
dots.forEach(function (d, i) {
d.classList.toggle('reviews-dot--active', i === current);
});
}
function startAuto() {
stopAuto();
timer = setInterval(function () {
goTo(current + 1);
}, AUTO_DELAY);
}
function stopAuto() {
if (timer) clearInterval(timer);
}
section.querySelector('.reviews-nav--prev').addEventListener('click', function () {
goTo(current - 1);
stopAuto(); startAuto();
});
section.querySelector('.reviews-nav--next').addEventListener('click', function () {
goTo(current + 1);
stopAuto(); startAuto();
});
dots.forEach(function (dot) {
dot.addEventListener('click', function () {
goTo(parseInt(dot.getAttribute('data-index')));
stopAuto(); startAuto();
});
});
/* pause on hover */
section.addEventListener('mouseenter', stopAuto);
section.addEventListener('mouseleave', startAuto);
/* touch swipe */
var touchStartX = 0;
track.addEventListener('touchstart', function (e) {
touchStartX = e.touches[0].clientX;
stopAuto();
}, { passive: true });
track.addEventListener('touchend', function (e) {
var diff = touchStartX - e.changedTouches[0].clientX;
if (Math.abs(diff) > 40) goTo(diff > 0 ? current + 1 : current - 1);
startAuto();
}, { passive: true });
window.addEventListener('resize', function () {
setCardWidths();
goTo(current);
});
setCardWidths();
goTo(0);
startAuto();
})();
</script>
{% schema %}
{
"name": "Customer Reviews",
"tag": "section",
"class": "section-customer-reviews",
"max_blocks": 9,
"settings": [
{
"type": "header",
"content": "Background & Layout"
},
{
"type": "color",
"id": "background_color",
"label": "Background color",
"default": "#FAF8F3"
},
{
"type": "range",
"id": "max_width",
"min": 600,
"max": 1400,
"step": 20,
"unit": "px",
"label": "Section max width",
"default": 1100
},
{
"type": "range",
"id": "padding_top",
"min": 0,
"max": 120,
"step": 4,
"unit": "px",
"label": "Padding top",
"default": 64
},
{
"type": "range",
"id": "padding_bottom",
"min": 0,
"max": 120,
"step": 4,
"unit": "px",
"label": "Padding bottom",
"default": 64
},
{
"type": "header",
"content": "Section Header"
},
{
"type": "text",
"id": "eyebrow",
"label": "Eyebrow text",
"default": "Happy Customers"
},
{
"type": "text",
"id": "section_title",
"label": "Section title",
"default": "What people are saying"
},
{
"type": "text",
"id": "section_subtitle",
"label": "Subtitle (optional)"
},
{
"type": "header",
"content": "Header Colors"
},
{
"type": "color",
"id": "eyebrow_color",
"label": "Eyebrow color",
"default": "#8A7A5C"
},
{
"type": "color",
"id": "title_color",
"label": "Title color",
"default": "#3B3226"
},
{
"type": "range",
"id": "title_size",
"min": 20,
"max": 48,
"step": 2,
"unit": "px",
"label": "Title font size",
"default": 32
},
{
"type": "header",
"content": "Carousel"
},
{
"type": "range",
"id": "cards_visible",
"min": 1,
"max": 4,
"step": 1,
"label": "Cards visible at once",
"default": 3
},
{
"type": "range",
"id": "autoplay_speed",
"min": 2,
"max": 10,
"step": 1,
"unit": "s",
"label": "Auto-slide speed",
"default": 4
},
{
"type": "header",
"content": "Cards"
},
{
"type": "color",
"id": "card_bg",
"label": "Card background",
"default": "#FFFFFF"
},
{
"type": "color",
"id": "card_border_color",
"label": "Card border color",
"default": "#E3DDD0"
},
{
"type": "range",
"id": "card_gap",
"min": 8,
"max": 40,
"step": 4,
"unit": "px",
"label": "Gap between cards",
"default": 20
},
{
"type": "range",
"id": "card_padding",
"min": 12,
"max": 40,
"step": 4,
"unit": "px",
"label": "Card padding",
"default": 24
},
{
"type": "header",
"content": "Stars"
},
{
"type": "color",
"id": "star_color",
"label": "Star color",
"default": "#8A7A5C"
},
{
"type": "range",
"id": "star_size",
"min": 12,
"max": 24,
"step": 1,
"unit": "px",
"label": "Star size",
"default": 16
},
{
"type": "header",
"content": "Review Text"
},
{
"type": "color",
"id": "review_text_color",
"label": "Review text color",
"default": "#6B5C40"
},
{
"type": "range",
"id": "review_text_size",
"min": 12,
"max": 18,
"step": 1,
"unit": "px",
"label": "Review text size",
"default": 14
},
{
"type": "header",
"content": "Author"
},
{
"type": "color",
"id": "avatar_bg",
"label": "Avatar background",
"default": "#F0EBE0"
},
{
"type": "color",
"id": "avatar_text_color",
"label": "Avatar initials color",
"default": "#8A7A5C"
},
{
"type": "color",
"id": "author_name_color",
"label": "Author name color",
"default": "#3B3226"
},
{
"type": "color",
"id": "author_location_color",
"label": "Author location color",
"default": "#8A7A5C"
}
],
"blocks": [
{
"type": "review",
"name": "Review",
"settings": [
{
"type": "range",
"id": "rating",
"min": 1,
"max": 5,
"step": 1,
"label": "Star rating",
"default": 5
},
{
"type": "textarea",
"id": "review_text",
"label": "Review text",
"default": "দেখতে যত সুন্দর, ব্যবহারেও তত কাজের।"
},
{
"type": "text",
"id": "author_name",
"label": "Author name",
"default": "Rafiq Ahmed"
},
{
"type": "text",
"id": "author_location",
"label": "Author location",
"default": "Dhaka, Dhanmondi"
},
{
"type": "image_picker",
"id": "avatar",
"label": "Author photo (optional)",
"info": "If left empty initials will show instead"
}
]
}
],
"presets": [
{
"name": "Customer Reviews",
"blocks": [
{
"type": "review",
"settings": {
"rating": 5,
"review_text": "দেখতে যত সুন্দর, ব্যবহারেও তত কাজের। Key holder টা দরজার পাশে রাখার পর থেকে চাবি আর হারাই না।",
"author_name": "Rafiq Ahmed",
"author_location": "Dhaka, Dhanmondi"
}
},
{
"type": "review",
"settings": {
"rating": 5,
"review_text": "The Half-Moon shelf with the glass vase is exactly what my living room needed. Looks like something from an interior design magazine.",
"author_name": "Sadia Rahman",
"author_location": "Dhaka, Gulshan"
}
},
{
"type": "review",
"settings": {
"rating": 5,
"review_text": "Packaging was beautiful — came as a gift for my sister's new apartment. She loved it. The Globe Vase L-Stand is perfect for her desk.",
"author_name": "Nadia Khan",
"author_location": "Chattogram"
}
}
]
}
]
}
{% endschema %}